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.
@@ -0,0 +1,2 @@
1
+ GRIDPANE_API_TOKEN=your_api_token_here
2
+ GRIDPANE_API_URL=https://my.gridpane.com/api
@@ -0,0 +1,6 @@
1
+ .env
2
+ __pycache__/
3
+ *.pyc
4
+ venv/
5
+ .venv/
6
+ .DS_Store
@@ -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,3 @@
1
+ """GridPane MCP Server — Model Context Protocol server for the GridPane hosting API."""
2
+
3
+ __version__ = "0.1.0"
@@ -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()