RunCloud MCP Server

RunCloud MCP Server

Exposes the RunCloud v3 API to AI assistants, allowing natural language querying and management of servers with safety profiles for destructive operations.

Category
访问服务器

README

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

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

Claude Code (local, recommended)

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.

推荐服务器

Baidu Map

Baidu Map

百度地图核心API现已全面兼容MCP协议,是国内首家兼容MCP协议的地图服务商。

官方
精选
JavaScript
Playwright MCP Server

Playwright MCP Server

一个模型上下文协议服务器,它使大型语言模型能够通过结构化的可访问性快照与网页进行交互,而无需视觉模型或屏幕截图。

官方
精选
TypeScript
Magic Component Platform (MCP)

Magic Component Platform (MCP)

一个由人工智能驱动的工具,可以从自然语言描述生成现代化的用户界面组件,并与流行的集成开发环境(IDE)集成,从而简化用户界面开发流程。

官方
精选
本地
TypeScript
Audiense Insights MCP Server

Audiense Insights MCP Server

通过模型上下文协议启用与 Audiense Insights 账户的交互,从而促进营销洞察和受众数据的提取和分析,包括人口统计信息、行为和影响者互动。

官方
精选
本地
TypeScript
VeyraX

VeyraX

一个单一的 MCP 工具,连接你所有喜爱的工具:Gmail、日历以及其他 40 多个工具。

官方
精选
本地
graphlit-mcp-server

graphlit-mcp-server

模型上下文协议 (MCP) 服务器实现了 MCP 客户端与 Graphlit 服务之间的集成。 除了网络爬取之外,还可以将任何内容(从 Slack 到 Gmail 再到播客订阅源)导入到 Graphlit 项目中,然后从 MCP 客户端检索相关内容。

官方
精选
TypeScript
Kagi MCP Server

Kagi MCP Server

一个 MCP 服务器,集成了 Kagi 搜索功能和 Claude AI,使 Claude 能够在回答需要最新信息的问题时执行实时网络搜索。

官方
精选
Python
e2b-mcp-server

e2b-mcp-server

使用 MCP 通过 e2b 运行代码。

官方
精选
Neon MCP Server

Neon MCP Server

用于与 Neon 管理 API 和数据库交互的 MCP 服务器

官方
精选
Exa MCP Server

Exa MCP Server

模型上下文协议(MCP)服务器允许像 Claude 这样的 AI 助手使用 Exa AI 搜索 API 进行网络搜索。这种设置允许 AI 模型以安全和受控的方式获取实时的网络信息。

官方
精选