Skip to main content
Glama
josephmcgarvey

RunCloud MCP Server

RunCloud MCP Server

A Model Context Protocol server that exposes the RunCloud v3 API to AI assistants like Claude Code and Claude Desktop. Ask questions about your servers in plain language, and perform management actions with explicit guard rails around anything destructive.

Built on Laravel MCP.

You:    Which services are running on my production server?
Claude: (calls runcloud-services) NGiNX, MariaDB, Redis and Supervisor are running.
        Memcached and Beanstalk are stopped.

This controls live infrastructure. Depending on the configured profile, the tools here can restart services, deploy code, and delete web applications and databases. Read Tool profiles and Safety model before connecting it to a production RunCloud account.

Requirements

  • PHP 8.3+

  • Composer

  • A database (PostgreSQL, MySQL, or SQLite) — only used for API tokens when running the HTTP transport

  • A RunCloud account and API key

Related MCP server: SpinupWP MCP Server

Installation

git clone https://github.com/josephmcgarvey/runcloud-mcp-server.git
cd runcloud-mcp-server

composer install
cp .env.example .env
php artisan key:generate
php artisan migrate

Then configure .env:

RUNCLOUD_API_TOKEN=your-token-here
RUNCLOUD_MCP_PROFILE=standard

RUNCLOUD_MCP_PROFILE decides which tools exist at all. Start with readonly while you are getting a feel for it — see Tool profiles.

Getting a RunCloud API key

In the RunCloud dashboard, go to Workspace → Settings → API Key and create one. The same screen shows your rate limits. The key is a bearer token with full access to everything in that workspace, so treat it like a root password.

Verify it works:

php artisan tinker --execute 'app(App\Services\RunCloud\RunCloudClient::class)->get("ping");'
# => ["message" => "pong"]

Connecting

The simplest setup. Claude Code launches the server over stdio — no HTTP, no auth layer, nothing exposed to the network:

claude mcp add runcloud -- php /absolute/path/to/runcloud-mcp-server/artisan mcp:start runcloud

Use --scope user to make it available in every directory rather than just the current project. Restart Claude Code, then check /mcp to confirm the tools loaded.

Claude Desktop and remote clients (HTTP)

Claude Desktop cannot launch a local process on a remote machine, so it needs the HTTP transport. That endpoint is registered at /mcp and guarded by Laravel Sanctum.

Create a user and issue a token:

php artisan tinker --execute 'App\Models\User::create([
    "name" => "You", "email" => "you@example.com", "password" => "a-strong-password",
]);'

php artisan mcp:token you@example.com --name=claude-desktop

The token is displayed once. Connect a remote Claude Code client with:

claude mcp add --transport http runcloud https://your-app.example.com/mcp \
  --header "Authorization: Bearer YOUR_SANCTUM_TOKEN"

Claude Desktop (OAuth)

Claude Desktop's custom connectors only speak OAuth — there is no field for a static bearer token — so Sanctum alone cannot serve them. Laravel Passport provides the OAuth server, and Mcp::oauthRoutes() publishes discovery and dynamic client registration.

Point Claude Desktop at https://your-app.example.com/mcp under Settings → Connectors → Add custom connector, leaving the OAuth client fields blank — the server supports dynamic registration, so it configures itself. It discovers the authorization server, registers, and sends you through sign-in and an approval screen.

The /mcp route accepts both guards, declared in this order:

->middleware('auth:sanctum,api')

The order matters. Passport's guard aborts the chain when it cannot resolve a token, so listing it first causes valid Sanctum tokens to be rejected. With Sanctum first, personal access tokens authenticate directly and anything Sanctum cannot resolve falls through to Passport. A test pins this behaviour.

Passport renders a consent screen but ships no view for it, and it requires a signed-in session, so this application provides both: a login page at /login and a consent screen at resources/views/oauth/authorize.blade.php. Neither uses Vite — they must render correctly on a fresh deployment, and an unstyled consent screen is a poor place to be asking someone to grant access to infrastructure.

Create an account from the console; there is no self-service registration, because signing in leads to server management tooling:

php artisan tinker --execute 'App\Models\User::create([
    "name" => "You", "email" => "you@example.com", "password" => "a-strong-password",
]);'

This flow is verified working with Claude Desktop against a live deployment: discovery, dynamic client registration, the redirect to sign in, the consent screen, approval, and a connected server.

When the connector will not register

Claude Desktop reports the same message — "Couldn't register with … sign-in service" — whether the server refused registration or was never reached at all, so start by ruling out the network path:

curl https://your-app.example.com/.well-known/oauth-authorization-server

JSON with an issuer field means the path is open and the problem is in OAuth. A timeout or connection refused means the request is not arriving, and nothing in this application can fix it. Behind a reverse proxy, check the proxy points at the port the application actually listens on — a proxy that is publicly reachable but forwarding to the wrong port fails exactly this way.

Then confirm APP_URL matches the public hostname. Every endpoint in the discovery document is derived from it, so a stale value sends the client somewhere that does not answer.

Never expose /mcp without authentication. Anyone who can reach it can control your servers. Note that the login page is public by necessity — it is where the OAuth redirect lands — so any account on this application should have a strong password.

Available tools

Forty-five tools, subject to the configured profile. Read and destructive operations are deliberately split into separate tools so that MCP annotations stay accurate.

Read-only

Tool

Actions

runcloud-servers

list, get, stats (counts), health (live RAM/disk/CPU), hardware

runcloud-webapps

list (with search and sorting), get

runcloud-services

list services with running state, memory, CPU, version

runcloud-databases

list, get, grants

runcloud-database-users

list, get

runcloud-domains

list, get

runcloud-ssl

status, method (basic vs advanced, AutoSSL)

runcloud-system-users

list, get

runcloud-git

Repository, branch, auto-deploy state, deployment script

runcloud-cron

list, get

runcloud-supervisor

list, get, status (live process states)

runcloud-webapp-settings

PHP/FPM settings and linked databases

runcloud-firewall

Server firewall rules, summarised by port

Write

Tool

Notes

runcloud-create-webapp

Custom/empty app for Laravel, APIs, static sites

runcloud-create-wordpress

Full WordPress install; generates both passwords

runcloud-create-database

RunCloud cannot rename a database afterwards

runcloud-create-database-user

Generates a strong password; returned exactly once

runcloud-add-domain

Configures neither DNS nor HTTPS

runcloud-create-cron

Takes a normal five-field cron expression

runcloud-create-supervisor

Long-running processes such as queue workers

runcloud-create-system-user

Generates the SSH/SFTP password; sudo off by default

runcloud-add-firewall-rule

Stages a rule; refuses duplicates and malformed addresses

Destructive — require confirm=true

Tool

Notes

runcloud-control-service

start, stop, restart, reload. Only stop/restart need confirmation

runcloud-database-grant

grant needs no confirmation; revoke does

runcloud-database-user-password

Resets a password, breaking apps using the old one

runcloud-toggle-cron

Flips state; re-reads the job first to avoid inverting the intent

runcloud-update-webapp-settings

Merges over current values; reloads the FPM pool

runcloud-change-php-version

Applies immediately, no staging step or rollback

runcloud-system-user-password

Breaks anything using the old SSH/SFTP password

runcloud-generate-deployment-key

Replaces any existing key, breaking deploys using it

runcloud-deploy-firewall

Applies staged rules; refuses to leave no SSH route in

runcloud-git-clone

Writes into the live application root

runcloud-git-deploy

deploy (runs the script) or branch (switches branches)

runcloud-git-deploy-script

Replaces the script outright; can enable auto-deploy

runcloud-git-remove

Also requires a matching repository

runcloud-install-ssl

Let's Encrypt or custom. Prefer the staging environment while testing

runcloud-delete-webapp

runcloud-delete-database

deleteUser defaults to false

runcloud-delete-database-user

runcloud-delete-domain

Also requires a matching domainName

runcloud-delete-ssl

HTTPS stops working

runcloud-delete-cron

Also requires a matching label

runcloud-delete-supervisor

Also requires a matching label; queued work stops

runcloud-delete-system-user

Refuses while the user still owns web apps

runcloud-delete-firewall-rule

Also requires a matching ipAddress; refuses the last SSH rule

Tool profiles

Every registered tool's schema is sent to the client on each request, so the tool list has a standing context cost. It is also the outer boundary on what the model can do at all: a tool that is not registered cannot be called, whatever the prompt says.

Set RUNCLOUD_MCP_PROFILE in .env:

Profile

Tools

Approx. context

Allows

readonly

13

~4,600 tokens

Nothing that changes anything

standard (default)

35

~13,200 tokens

Reads, creation, modification, service control

full

45

~16,100 tokens

Everything, including deletions

standard withholds the ten tools that destroy something the API cannot recreate. It still allows restarting services, deploying, and changing settings — those are disruptive but recoverable.

An unrecognised value falls back to standard, so a typo cannot silently widen access.

Deleting servers

There is no tool for it, under any profile. DELETE /servers/{id} exists in the RunCloud API and is deliberately not exposed here: destroying an entire machine is not an operation worth reaching through a language model, and no confirmation argument makes it safe enough to be worth the risk. A test asserts the tool has not been reintroduced.

Safety model

Five independent layers, because a language model calling infrastructure APIs will eventually misread an instruction.

Profiles. The narrowest layer, and the only one that does not depend on the model behaving reasonably: a withheld tool is absent from the schema entirely. See Tool profiles.

Annotations. Tools are marked readOnlyHint or destructiveHint so the client can warn before running them. This is only a hint — a model can still call the tool — which is why the other layers exist.

Explicit confirmation. Destructive tools hard-refuse unless confirm=true is present in the payload, and the server instructions tell the model never to set it on its own initiative. Refusals return an error explaining what would have been affected, so the model must come back to you.

Name verification. Deleting a server, database, or database user also requires passing its exact name. If the name does not match what the API reports, the call is refused before anything is deleted. This catches a hallucinated or stale ID.

Credential redaction. RunCloud embeds Git deploy keys (pullKey1, pullKey2) directly in web application responses. These are stripped in RunCloudClient::decode() — at the transport boundary rather than per tool — so every current and future tool is covered. The redaction recurses, because RunCloud's response envelope is inconsistent: list endpoints wrap results in data while single-resource and delete endpoints return bare objects.

Database passwords are generated server-side rather than accepted from the model, which produces low-entropy passwords when asked to invent one. Generated passwords are 40 characters and alphanumeric, so they survive .env files and DSN connection strings without escaping.

Architecture

There is no intermediate REST API. The Laravel application is the MCP server:

Claude  ──JSON-RPC──▶  Laravel MCP  ──▶  MCP Tools  ──▶  RunCloudClient  ──HTTPS──▶  RunCloud v3
                       routes/ai.php     app/Mcp/Tools    app/Services/RunCloud

RunCloudClient owns authentication, retries, pagination clamping, error translation, and redaction. Tools own endpoint paths and input schemas.

Adding a resource means writing a tool that calls the client and registering it in App\Mcp\Servers\RunCloudServer.

Postman collection

postman/ contains a collection and environment covering 57 RunCloud v3 endpoints, useful for exercising the API directly when a tool misbehaves and you need to see the raw response.

Import both files, set apiToken in the environment, and run 00 · Connectivity > Ping.

Requests are annotated with the API's sharper edges — the inconsistent response envelope, the PATCH/PUT collision on the Git script endpoint, the DELETE that requires a JSON body, and the cron toggle that flips rather than sets. Destructive requests are isolated in their own folder.

Testing

php artisan test

The suite covers the client (auth headers, pagination clamping, error translation, redaction) and every tool, including that each destructive guard refuses for the right reason. HTTP calls are faked, so the tests never touch a real RunCloud account.

One test scans the server instructions and tool descriptions for runcloud-* references and fails if any does not resolve to a registered tool — prose that steers the model by tool name should not drift away from the code.

Deployment

This is an ordinary Laravel application and deploys anywhere Laravel runs, including Laravel Cloud. Set RUNCLOUD_API_TOKEN and APP_URL, run migrations, and ensure /mcp sits behind authentication.

Passport keys are not in the repository. They live in storage/oauth-*.key, which is gitignored because a private key must never be committed. Every environment needs its own, so your deploy pipeline must run:

php artisan passport:keys

Alternatively, supply them as PASSPORT_PRIVATE_KEY and PASSPORT_PUBLIC_KEY environment variables. Without keys, Passport throws LogicException: Invalid key supplied and every request to /mcp returns 500 rather than a 401.

APP_URL must match the public hostname. OAuth discovery advertises endpoints derived from it, and clients reject an authorization server served over plain HTTP. Behind a TLS-terminating proxy the application trusts X-Forwarded-* headers so generated URLs use https.

Coverage

Currently implemented: servers, web applications (create custom and WordPress, list, get, delete, settings), services, databases, database users, domains, basic SSL, system users, Git deployment, cron jobs, Supervisor, and the server firewall.

Not yet implemented: cloning and alias apps, advanced per-domain SSL, backups and snapshots, server provisioning, the per-application WAF, third-party integrations, atomic deployments, and script installers. These follow the same pattern — a tool class calling RunCloudClient — and the client and test scaffolding are reusable as-is.

API key permissions

RunCloud scopes API keys per resource type. A key that can list servers may still be refused on Git, returning 403. If a tool reports that the key lacks permission, enable the relevant scope under Workspace → Settings → API Key rather than assuming the tool is broken.

Security

The RunCloud API key grants full control over every server in the workspace. Do not commit it; .env is gitignored and only .env.example is tracked.

If you find a security issue, please open an issue or contact the maintainer rather than filing a public exploit.

License

MIT.

A
license - permissive license
-
quality - not tested
B
maintenance

Maintenance

Maintainers
Response time
Release cycle
Releases (12mo)
Commit activity

Resources

Unclaimed servers have limited discoverability.

Looking for Admin?

If you are the server author, to access and configure the admin panel.

Related MCP Servers

  • A
    license
    A
    quality
    D
    maintenance
    Enables AI assistants to manage SpinupWP infrastructure, including servers, WordPress sites, and SSH keys through the SpinupWP v1 JSON API. Users can perform actions like provisioning sites, purging caches, and restarting services via natural language commands.
    17
    MIT
  • F
    license
    -
    quality
    D
    maintenance
    Enables AI assistants to manage Cloudways infrastructure, including servers, applications, monitoring, and security via the Cloudways API.

View all related MCP servers

Related MCP Connectors

  • Operate Linux, macOS and Windows from your LLM. Every action runs through an auditable allowlist.

  • Let AI operate servers without SSH. Choose actions, approve risky changes, and audit every step.

  • Security-first WordPress MCP server. 129 tools for Claude, ChatGPT, Gemini. Free on wp.org.

View all MCP Connectors

Latest Blog Posts

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/josephmcgarvey/runcloud-mcp-server'

If you have feedback or need assistance with the MCP directory API, please join our Discord server