gridpane-mcp 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- gridpane_mcp-0.1.0/.env.example +2 -0
- gridpane_mcp-0.1.0/.gitignore +6 -0
- gridpane_mcp-0.1.0/.python-version +1 -0
- gridpane_mcp-0.1.0/LICENSE +21 -0
- gridpane_mcp-0.1.0/PKG-INFO +135 -0
- gridpane_mcp-0.1.0/README.md +112 -0
- gridpane_mcp-0.1.0/pyproject.toml +36 -0
- gridpane_mcp-0.1.0/src/gridpane_mcp/__init__.py +3 -0
- gridpane_mcp-0.1.0/src/gridpane_mcp/server.py +217 -0
- gridpane_mcp-0.1.0/uv.lock +692 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.12
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Fenil Patel
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: gridpane-mcp
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server for the GridPane hosting platform API. Manage WordPress servers and sites through Claude, Cursor, or any MCP client.
|
|
5
|
+
Project-URL: Homepage, https://github.com/fenil0020/gridpane-mcp
|
|
6
|
+
Project-URL: Repository, https://github.com/fenil0020/gridpane-mcp
|
|
7
|
+
Project-URL: Issues, https://github.com/fenil0020/gridpane-mcp/issues
|
|
8
|
+
Author-email: Fenil Patel <fenil@supple.com.au>
|
|
9
|
+
License-Expression: MIT
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
Keywords: gridpane,hosting,mcp,model-context-protocol,wordpress
|
|
12
|
+
Classifier: Development Status :: 4 - Beta
|
|
13
|
+
Classifier: Intended Audience :: Developers
|
|
14
|
+
Classifier: Intended Audience :: System Administrators
|
|
15
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
16
|
+
Classifier: Programming Language :: Python :: 3
|
|
17
|
+
Classifier: Topic :: Internet :: WWW/HTTP :: Site Management
|
|
18
|
+
Classifier: Topic :: System :: Systems Administration
|
|
19
|
+
Requires-Python: >=3.10
|
|
20
|
+
Requires-Dist: httpx>=0.27.0
|
|
21
|
+
Requires-Dist: mcp[cli]>=1.2.0
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
|
|
24
|
+
# gridpane-mcp
|
|
25
|
+
|
|
26
|
+
A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server for the [GridPane](https://gridpane.com/) hosting platform API. Manage your WordPress servers and sites through Claude, Cursor, or any MCP-compatible client.
|
|
27
|
+
|
|
28
|
+
## Features
|
|
29
|
+
|
|
30
|
+
### Server Management
|
|
31
|
+
- **list_servers** — List all servers with IPs, status, and site counts
|
|
32
|
+
- **get_server** — Get detailed server info by ID
|
|
33
|
+
- **search_server** — Find servers by name (partial match)
|
|
34
|
+
- **restart_nginx** — Restart Nginx on a server
|
|
35
|
+
- **restart_php** — Restart PHP-FPM for a specific PHP version
|
|
36
|
+
- **restart_mysql** — Restart MySQL/MariaDB/Percona
|
|
37
|
+
|
|
38
|
+
### Site Management
|
|
39
|
+
- **list_sites** — List all WordPress sites across all servers
|
|
40
|
+
- **get_site** — Get detailed site info by ID
|
|
41
|
+
- **search_site** — Find sites by domain name (partial match)
|
|
42
|
+
- **toggle_ssl** — Toggle SSL on/off
|
|
43
|
+
- **toggle_site_cache** — Toggle Nginx caching
|
|
44
|
+
- **purge_site_cache** — Purge all server-level caches
|
|
45
|
+
- **toggle_debug** — Toggle WordPress debug mode
|
|
46
|
+
- **change_php_version** — Change PHP version (7.4, 8.0, 8.1, 8.2, 8.3)
|
|
47
|
+
- **get_sso_link** — Get a Single Sign-On link to wp-admin
|
|
48
|
+
- **trigger_backup** — Trigger a manual backup
|
|
49
|
+
|
|
50
|
+
## Prerequisites
|
|
51
|
+
|
|
52
|
+
- A [GridPane](https://gridpane.com/) account (Developer plan or above)
|
|
53
|
+
- A GridPane API token (Settings > GridPane API > Create New Token)
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
pip install gridpane-mcp
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Or with [uv](https://docs.astral.sh/uv/):
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
uv pip install gridpane-mcp
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
## Configuration
|
|
68
|
+
|
|
69
|
+
Set your GridPane API token as an environment variable:
|
|
70
|
+
|
|
71
|
+
```bash
|
|
72
|
+
export GRIDPANE_API_TOKEN="your_token_here"
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Claude Code
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
claude mcp add gridpane -e GRIDPANE_API_TOKEN=your_token_here -- gridpane-mcp
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
### Claude Desktop
|
|
82
|
+
|
|
83
|
+
Add to your `claude_desktop_config.json`:
|
|
84
|
+
|
|
85
|
+
```json
|
|
86
|
+
{
|
|
87
|
+
"mcpServers": {
|
|
88
|
+
"gridpane": {
|
|
89
|
+
"command": "gridpane-mcp",
|
|
90
|
+
"env": {
|
|
91
|
+
"GRIDPANE_API_TOKEN": "your_token_here"
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
### Cursor
|
|
99
|
+
|
|
100
|
+
Add to your Cursor MCP settings:
|
|
101
|
+
|
|
102
|
+
```json
|
|
103
|
+
{
|
|
104
|
+
"mcpServers": {
|
|
105
|
+
"gridpane": {
|
|
106
|
+
"command": "gridpane-mcp",
|
|
107
|
+
"env": {
|
|
108
|
+
"GRIDPANE_API_TOKEN": "your_token_here"
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## Usage Examples
|
|
116
|
+
|
|
117
|
+
Once connected, you can ask your AI assistant things like:
|
|
118
|
+
|
|
119
|
+
- "List all my GridPane servers"
|
|
120
|
+
- "Find sites with 'supple' in the domain"
|
|
121
|
+
- "Purge cache for site ID 661035"
|
|
122
|
+
- "Restart Nginx on the accesshealth server"
|
|
123
|
+
- "What PHP version is accesshealth.com.au running?"
|
|
124
|
+
- "Toggle debug mode on site 661035"
|
|
125
|
+
|
|
126
|
+
## Environment Variables
|
|
127
|
+
|
|
128
|
+
| Variable | Default | Description |
|
|
129
|
+
|----------|---------|-------------|
|
|
130
|
+
| `GRIDPANE_API_TOKEN` | (required) | Your GridPane API personal access token |
|
|
131
|
+
| `GRIDPANE_API_URL` | `https://my.gridpane.com/oauth/api/v1` | GridPane API base URL |
|
|
132
|
+
|
|
133
|
+
## License
|
|
134
|
+
|
|
135
|
+
MIT
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
# gridpane-mcp
|
|
2
|
+
|
|
3
|
+
A [Model Context Protocol (MCP)](https://modelcontextprotocol.io/) server for the [GridPane](https://gridpane.com/) hosting platform API. Manage your WordPress servers and sites through Claude, Cursor, or any MCP-compatible client.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
### Server Management
|
|
8
|
+
- **list_servers** — List all servers with IPs, status, and site counts
|
|
9
|
+
- **get_server** — Get detailed server info by ID
|
|
10
|
+
- **search_server** — Find servers by name (partial match)
|
|
11
|
+
- **restart_nginx** — Restart Nginx on a server
|
|
12
|
+
- **restart_php** — Restart PHP-FPM for a specific PHP version
|
|
13
|
+
- **restart_mysql** — Restart MySQL/MariaDB/Percona
|
|
14
|
+
|
|
15
|
+
### Site Management
|
|
16
|
+
- **list_sites** — List all WordPress sites across all servers
|
|
17
|
+
- **get_site** — Get detailed site info by ID
|
|
18
|
+
- **search_site** — Find sites by domain name (partial match)
|
|
19
|
+
- **toggle_ssl** — Toggle SSL on/off
|
|
20
|
+
- **toggle_site_cache** — Toggle Nginx caching
|
|
21
|
+
- **purge_site_cache** — Purge all server-level caches
|
|
22
|
+
- **toggle_debug** — Toggle WordPress debug mode
|
|
23
|
+
- **change_php_version** — Change PHP version (7.4, 8.0, 8.1, 8.2, 8.3)
|
|
24
|
+
- **get_sso_link** — Get a Single Sign-On link to wp-admin
|
|
25
|
+
- **trigger_backup** — Trigger a manual backup
|
|
26
|
+
|
|
27
|
+
## Prerequisites
|
|
28
|
+
|
|
29
|
+
- A [GridPane](https://gridpane.com/) account (Developer plan or above)
|
|
30
|
+
- A GridPane API token (Settings > GridPane API > Create New Token)
|
|
31
|
+
|
|
32
|
+
## Installation
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
pip install gridpane-mcp
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Or with [uv](https://docs.astral.sh/uv/):
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
uv pip install gridpane-mcp
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Configuration
|
|
45
|
+
|
|
46
|
+
Set your GridPane API token as an environment variable:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
export GRIDPANE_API_TOKEN="your_token_here"
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### Claude Code
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
claude mcp add gridpane -e GRIDPANE_API_TOKEN=your_token_here -- gridpane-mcp
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### Claude Desktop
|
|
59
|
+
|
|
60
|
+
Add to your `claude_desktop_config.json`:
|
|
61
|
+
|
|
62
|
+
```json
|
|
63
|
+
{
|
|
64
|
+
"mcpServers": {
|
|
65
|
+
"gridpane": {
|
|
66
|
+
"command": "gridpane-mcp",
|
|
67
|
+
"env": {
|
|
68
|
+
"GRIDPANE_API_TOKEN": "your_token_here"
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
### Cursor
|
|
76
|
+
|
|
77
|
+
Add to your Cursor MCP settings:
|
|
78
|
+
|
|
79
|
+
```json
|
|
80
|
+
{
|
|
81
|
+
"mcpServers": {
|
|
82
|
+
"gridpane": {
|
|
83
|
+
"command": "gridpane-mcp",
|
|
84
|
+
"env": {
|
|
85
|
+
"GRIDPANE_API_TOKEN": "your_token_here"
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## Usage Examples
|
|
93
|
+
|
|
94
|
+
Once connected, you can ask your AI assistant things like:
|
|
95
|
+
|
|
96
|
+
- "List all my GridPane servers"
|
|
97
|
+
- "Find sites with 'supple' in the domain"
|
|
98
|
+
- "Purge cache for site ID 661035"
|
|
99
|
+
- "Restart Nginx on the accesshealth server"
|
|
100
|
+
- "What PHP version is accesshealth.com.au running?"
|
|
101
|
+
- "Toggle debug mode on site 661035"
|
|
102
|
+
|
|
103
|
+
## Environment Variables
|
|
104
|
+
|
|
105
|
+
| Variable | Default | Description |
|
|
106
|
+
|----------|---------|-------------|
|
|
107
|
+
| `GRIDPANE_API_TOKEN` | (required) | Your GridPane API personal access token |
|
|
108
|
+
| `GRIDPANE_API_URL` | `https://my.gridpane.com/oauth/api/v1` | GridPane API base URL |
|
|
109
|
+
|
|
110
|
+
## License
|
|
111
|
+
|
|
112
|
+
MIT
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "gridpane-mcp"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
description = "MCP server for the GridPane hosting platform API. Manage WordPress servers and sites through Claude, Cursor, or any MCP client."
|
|
5
|
+
readme = "README.md"
|
|
6
|
+
license = "MIT"
|
|
7
|
+
requires-python = ">=3.10"
|
|
8
|
+
authors = [
|
|
9
|
+
{ name = "Fenil Patel", email = "fenil@supple.com.au" },
|
|
10
|
+
]
|
|
11
|
+
keywords = ["mcp", "gridpane", "wordpress", "hosting", "model-context-protocol"]
|
|
12
|
+
classifiers = [
|
|
13
|
+
"Development Status :: 4 - Beta",
|
|
14
|
+
"Intended Audience :: Developers",
|
|
15
|
+
"Intended Audience :: System Administrators",
|
|
16
|
+
"License :: OSI Approved :: MIT License",
|
|
17
|
+
"Programming Language :: Python :: 3",
|
|
18
|
+
"Topic :: Internet :: WWW/HTTP :: Site Management",
|
|
19
|
+
"Topic :: System :: Systems Administration",
|
|
20
|
+
]
|
|
21
|
+
dependencies = [
|
|
22
|
+
"httpx>=0.27.0",
|
|
23
|
+
"mcp[cli]>=1.2.0",
|
|
24
|
+
]
|
|
25
|
+
|
|
26
|
+
[project.urls]
|
|
27
|
+
Homepage = "https://github.com/fenil0020/gridpane-mcp"
|
|
28
|
+
Repository = "https://github.com/fenil0020/gridpane-mcp"
|
|
29
|
+
Issues = "https://github.com/fenil0020/gridpane-mcp/issues"
|
|
30
|
+
|
|
31
|
+
[project.scripts]
|
|
32
|
+
gridpane-mcp = "gridpane_mcp.server:main"
|
|
33
|
+
|
|
34
|
+
[build-system]
|
|
35
|
+
requires = ["hatchling"]
|
|
36
|
+
build-backend = "hatchling.build"
|
|
@@ -0,0 +1,217 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
import logging
|
|
5
|
+
import httpx
|
|
6
|
+
from mcp.server.fastmcp import FastMCP
|
|
7
|
+
|
|
8
|
+
API_URL = os.getenv("GRIDPANE_API_URL", "https://my.gridpane.com/oauth/api/v1")
|
|
9
|
+
API_TOKEN = os.getenv("GRIDPANE_API_TOKEN", "")
|
|
10
|
+
|
|
11
|
+
logging.basicConfig(level=logging.INFO, stream=sys.stderr)
|
|
12
|
+
logger = logging.getLogger("gridpane-mcp")
|
|
13
|
+
|
|
14
|
+
mcp = FastMCP("gridpane")
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def _headers():
|
|
18
|
+
return {
|
|
19
|
+
"Authorization": f"Bearer {API_TOKEN}",
|
|
20
|
+
"Accept": "application/json",
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
async def _get(path: str, params: dict | None = None) -> dict:
|
|
25
|
+
async with httpx.AsyncClient(timeout=30) as client:
|
|
26
|
+
r = await client.get(f"{API_URL}/{path}", headers=_headers(), params=params)
|
|
27
|
+
r.raise_for_status()
|
|
28
|
+
return r.json()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
async def _post(path: str, data: dict | None = None) -> dict:
|
|
32
|
+
async with httpx.AsyncClient(timeout=60) as client:
|
|
33
|
+
r = await client.post(f"{API_URL}/{path}", headers=_headers(), json=data)
|
|
34
|
+
r.raise_for_status()
|
|
35
|
+
return r.json()
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
async def _delete(path: str) -> dict:
|
|
39
|
+
async with httpx.AsyncClient(timeout=60) as client:
|
|
40
|
+
r = await client.delete(f"{API_URL}/{path}", headers=_headers())
|
|
41
|
+
r.raise_for_status()
|
|
42
|
+
return r.json()
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ── Server Tools ──────────────────────────────────────────────
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
@mcp.tool()
|
|
49
|
+
async def list_servers() -> str:
|
|
50
|
+
"""List all GridPane servers with their IPs, status, and site counts."""
|
|
51
|
+
data = await _get("server", {"per_page": 100})
|
|
52
|
+
lines = []
|
|
53
|
+
for s in data["data"]:
|
|
54
|
+
status = s.get("status") or "unknown"
|
|
55
|
+
sites = len(s.get("sites", []))
|
|
56
|
+
lines.append(
|
|
57
|
+
f'ID: {s["id"]} | {s["label"]:40s} | {s.get("ip",""):16s} | '
|
|
58
|
+
f'{status:8s} | sites={sites}'
|
|
59
|
+
)
|
|
60
|
+
lines.insert(0, f"Total servers: {len(data['data'])}\n")
|
|
61
|
+
return "\n".join(lines)
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
@mcp.tool()
|
|
65
|
+
async def get_server(server_id: int) -> str:
|
|
66
|
+
"""Get detailed info for a specific server by its ID."""
|
|
67
|
+
data = await _get(f"server/{server_id}")
|
|
68
|
+
return json.dumps(data.get("data", data), indent=2)
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
@mcp.tool()
|
|
72
|
+
async def search_server(name: str) -> str:
|
|
73
|
+
"""Search for a server by name (partial match). Returns matching servers with their sites."""
|
|
74
|
+
data = await _get("server", {"per_page": 100})
|
|
75
|
+
matches = [s for s in data["data"] if name.lower() in s["label"].lower()]
|
|
76
|
+
if not matches:
|
|
77
|
+
return f"No servers found matching '{name}'"
|
|
78
|
+
lines = []
|
|
79
|
+
for s in matches:
|
|
80
|
+
sites = ", ".join(site["url"] for site in s.get("sites", []))
|
|
81
|
+
lines.append(
|
|
82
|
+
f'ID: {s["id"]} | {s["label"]} | {s.get("ip","")} | '
|
|
83
|
+
f'{s.get("status") or "unknown"}\n Sites: {sites}'
|
|
84
|
+
)
|
|
85
|
+
return "\n\n".join(lines)
|
|
86
|
+
|
|
87
|
+
|
|
88
|
+
# ── Site Tools ────────────────────────────────────────────────
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
@mcp.tool()
|
|
92
|
+
async def list_sites(per_page: int = 200) -> str:
|
|
93
|
+
"""List all WordPress sites across all servers."""
|
|
94
|
+
data = await _get("site", {"per_page": per_page})
|
|
95
|
+
lines = []
|
|
96
|
+
for s in data["data"]:
|
|
97
|
+
lines.append(
|
|
98
|
+
f'ID: {s["id"]} | {s["url"]:50s} | type={s.get("type",""):10s} | '
|
|
99
|
+
f'php={s.get("php_version","")} | ssl={s.get("is_ssl","")} | '
|
|
100
|
+
f'server_id={s.get("server_id","")}'
|
|
101
|
+
)
|
|
102
|
+
lines.insert(0, f"Total sites: {len(data['data'])}\n")
|
|
103
|
+
return "\n".join(lines)
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
@mcp.tool()
|
|
107
|
+
async def get_site(site_id: int) -> str:
|
|
108
|
+
"""Get detailed info for a specific site by its ID."""
|
|
109
|
+
data = await _get(f"site/{site_id}")
|
|
110
|
+
return json.dumps(data.get("data", data), indent=2)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
@mcp.tool()
|
|
114
|
+
async def search_site(domain: str) -> str:
|
|
115
|
+
"""Search for a site by domain name (partial match)."""
|
|
116
|
+
data = await _get("site", {"per_page": 500})
|
|
117
|
+
matches = [s for s in data["data"] if domain.lower() in s["url"].lower()]
|
|
118
|
+
if not matches:
|
|
119
|
+
return f"No sites found matching '{domain}'"
|
|
120
|
+
lines = []
|
|
121
|
+
for s in matches:
|
|
122
|
+
lines.append(
|
|
123
|
+
f'ID: {s["id"]} | {s["url"]} | type={s.get("type","")} | '
|
|
124
|
+
f'php={s.get("php_version","")} | ssl={s.get("is_ssl","")} | '
|
|
125
|
+
f'server_id={s.get("server_id","")}'
|
|
126
|
+
)
|
|
127
|
+
return "\n".join(lines)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
# ── Site Operations ───────────────────────────────────────────
|
|
131
|
+
|
|
132
|
+
|
|
133
|
+
@mcp.tool()
|
|
134
|
+
async def toggle_ssl(site_id: int) -> str:
|
|
135
|
+
"""Toggle SSL on/off for a site."""
|
|
136
|
+
data = await _post(f"site/{site_id}/toggle-ssl")
|
|
137
|
+
return json.dumps(data, indent=2)
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
@mcp.tool()
|
|
141
|
+
async def toggle_site_cache(site_id: int) -> str:
|
|
142
|
+
"""Toggle Nginx caching for a site."""
|
|
143
|
+
data = await _post(f"site/{site_id}/toggle-nginx-caching")
|
|
144
|
+
return json.dumps(data, indent=2)
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@mcp.tool()
|
|
148
|
+
async def purge_site_cache(site_id: int) -> str:
|
|
149
|
+
"""Purge all server-level caches for a site."""
|
|
150
|
+
data = await _post(f"site/{site_id}/purge-all-cache")
|
|
151
|
+
return json.dumps(data, indent=2)
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
@mcp.tool()
|
|
155
|
+
async def toggle_debug(site_id: int) -> str:
|
|
156
|
+
"""Toggle WordPress debug mode for a site."""
|
|
157
|
+
data = await _post(f"site/{site_id}/toggle-wp-debug")
|
|
158
|
+
return json.dumps(data, indent=2)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@mcp.tool()
|
|
162
|
+
async def change_php_version(site_id: int, php_version: str) -> str:
|
|
163
|
+
"""Change PHP version for a site. Common versions: 7.4, 8.0, 8.1, 8.2, 8.3"""
|
|
164
|
+
data = await _post(f"site/{site_id}/change-php", {"php_version": php_version})
|
|
165
|
+
return json.dumps(data, indent=2)
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
# ── SSO / WP Admin ───────────────────────────────────────────
|
|
169
|
+
|
|
170
|
+
|
|
171
|
+
@mcp.tool()
|
|
172
|
+
async def get_sso_link(site_id: int) -> str:
|
|
173
|
+
"""Get a Single Sign-On link to access wp-admin for a site."""
|
|
174
|
+
data = await _post(f"site/{site_id}/sso-link")
|
|
175
|
+
return json.dumps(data, indent=2)
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
# ── Backup Tools ──────────────────────────────────────────────
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
@mcp.tool()
|
|
182
|
+
async def trigger_backup(site_id: int) -> str:
|
|
183
|
+
"""Trigger a manual backup for a site."""
|
|
184
|
+
data = await _post(f"site/{site_id}/backup")
|
|
185
|
+
return json.dumps(data, indent=2)
|
|
186
|
+
|
|
187
|
+
|
|
188
|
+
# ── Server Operations ────────────────────────────────────────
|
|
189
|
+
|
|
190
|
+
|
|
191
|
+
@mcp.tool()
|
|
192
|
+
async def restart_nginx(server_id: int) -> str:
|
|
193
|
+
"""Restart Nginx on a server."""
|
|
194
|
+
data = await _post(f"server/{server_id}/restart-nginx")
|
|
195
|
+
return json.dumps(data, indent=2)
|
|
196
|
+
|
|
197
|
+
|
|
198
|
+
@mcp.tool()
|
|
199
|
+
async def restart_php(server_id: int, php_version: str = "8.1") -> str:
|
|
200
|
+
"""Restart PHP-FPM on a server for a specific PHP version."""
|
|
201
|
+
data = await _post(f"server/{server_id}/restart-php", {"php_version": php_version})
|
|
202
|
+
return json.dumps(data, indent=2)
|
|
203
|
+
|
|
204
|
+
|
|
205
|
+
@mcp.tool()
|
|
206
|
+
async def restart_mysql(server_id: int) -> str:
|
|
207
|
+
"""Restart MySQL/MariaDB/Percona on a server."""
|
|
208
|
+
data = await _post(f"server/{server_id}/restart-mysql")
|
|
209
|
+
return json.dumps(data, indent=2)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
def main():
|
|
213
|
+
mcp.run(transport="stdio")
|
|
214
|
+
|
|
215
|
+
|
|
216
|
+
if __name__ == "__main__":
|
|
217
|
+
main()
|