sitebay-mcp 0.1.1751285665__py3-none-any.whl → 0.1.1757492007__py3-none-any.whl
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.
- sitebay_mcp/__init__.py +2 -2
- sitebay_mcp/client.py +34 -48
- sitebay_mcp/resources.py +6 -50
- sitebay_mcp/server.py +263 -339
- sitebay_mcp/tools/operations.py +45 -129
- sitebay_mcp/tools/sites.py +94 -87
- {sitebay_mcp-0.1.1751285665.dist-info → sitebay_mcp-0.1.1757492007.dist-info}/METADATA +67 -31
- sitebay_mcp-0.1.1757492007.dist-info/RECORD +14 -0
- sitebay_mcp-0.1.1751285665.dist-info/RECORD +0 -14
- {sitebay_mcp-0.1.1751285665.dist-info → sitebay_mcp-0.1.1757492007.dist-info}/WHEEL +0 -0
- {sitebay_mcp-0.1.1751285665.dist-info → sitebay_mcp-0.1.1757492007.dist-info}/entry_points.txt +0 -0
- {sitebay_mcp-0.1.1751285665.dist-info → sitebay_mcp-0.1.1757492007.dist-info}/licenses/LICENSE +0 -0
sitebay_mcp/tools/operations.py
CHANGED
@@ -10,7 +10,9 @@ from ..exceptions import SiteBayError
|
|
10
10
|
async def sitebay_site_shell_command(
|
11
11
|
client: SiteBayClient,
|
12
12
|
fqdn: str,
|
13
|
-
command: str
|
13
|
+
command: str,
|
14
|
+
cwd: Optional[str] = None,
|
15
|
+
auto_track_dir: Optional[bool] = None,
|
14
16
|
) -> str:
|
15
17
|
"""
|
16
18
|
Execute a shell command on a WordPress site (including WP-CLI commands).
|
@@ -23,7 +25,9 @@ async def sitebay_site_shell_command(
|
|
23
25
|
Command output or error message
|
24
26
|
"""
|
25
27
|
try:
|
26
|
-
result = await client.execute_shell_command(
|
28
|
+
result = await client.execute_shell_command(
|
29
|
+
fqdn, cmd=command, cwd=cwd, auto_track_dir=auto_track_dir
|
30
|
+
)
|
27
31
|
|
28
32
|
# Handle different response formats
|
29
33
|
if isinstance(result, dict):
|
@@ -38,6 +42,8 @@ async def sitebay_site_shell_command(
|
|
38
42
|
|
39
43
|
response = f"**Command executed on {fqdn}:**\n"
|
40
44
|
response += f"```bash\n{command}\n```\n\n"
|
45
|
+
if cwd is not None:
|
46
|
+
response += f"Working dir: {cwd}\n\n"
|
41
47
|
response += f"**Output:**\n```\n{output}\n```"
|
42
48
|
|
43
49
|
return response
|
@@ -50,26 +56,52 @@ async def sitebay_site_edit_file(
|
|
50
56
|
client: SiteBayClient,
|
51
57
|
fqdn: str,
|
52
58
|
file_path: str,
|
53
|
-
|
59
|
+
file_edit_using_search_replace_blocks: str,
|
54
60
|
) -> str:
|
55
61
|
"""
|
56
|
-
Edit a file in the site's wp-content directory.
|
62
|
+
Edit a file in the site's wp-content directory using diff-edit blocks.
|
57
63
|
|
58
64
|
Args:
|
59
65
|
fqdn: The fully qualified domain name of the site
|
60
|
-
file_path: Path to the file relative to wp-content (e.g., "themes/mytheme/style.css")
|
61
|
-
|
66
|
+
file_path: Path to the file relative to wp-content (e.g., "wp-content/themes/mytheme/style.css")
|
67
|
+
file_edit_using_search_replace_blocks: A single string containing blocks in the form:
|
68
|
+
<<<<<< SEARCH\nold text\n=======\nnew text\n>>>>>> REPLACE
|
62
69
|
|
63
70
|
Returns:
|
64
71
|
Success message or error
|
65
72
|
"""
|
66
73
|
try:
|
67
|
-
|
74
|
+
# Normalize path (server also handles this, but we pre-normalize to help users)
|
75
|
+
normalized_path = file_path.replace(
|
76
|
+
"/bitnami/wordpress/wp-content", "wp-content"
|
77
|
+
)
|
78
|
+
|
79
|
+
if not normalized_path.startswith("wp-content"):
|
80
|
+
return (
|
81
|
+
"❌ Invalid file_path: must start with 'wp-content/'. "
|
82
|
+
"Example: wp-content/themes/mytheme/style.css"
|
83
|
+
)
|
84
|
+
|
85
|
+
text = file_edit_using_search_replace_blocks
|
86
|
+
if (
|
87
|
+
"<<<<<<< SEARCH" not in text
|
88
|
+
or "=======" not in text
|
89
|
+
or ">>>>>> REPLACE" not in text
|
90
|
+
):
|
91
|
+
return (
|
92
|
+
"❌ Invalid diff-edit block format. Make sure blocks are in correct sequence, "
|
93
|
+
"and the markers are on separate lines:\n\n"
|
94
|
+
"<<<<<< SEARCH\n example old\n=======\n example new\n>>>>>> REPLACE\n"
|
95
|
+
)
|
96
|
+
|
97
|
+
result = await client.edit_file(
|
98
|
+
fqdn, normalized_path, text
|
99
|
+
)
|
68
100
|
|
69
101
|
response = f"✅ **File Updated Successfully**\n\n"
|
70
102
|
response += f"• **Site**: {fqdn}\n"
|
71
103
|
response += f"• **File**: wp-content/{file_path}\n"
|
72
|
-
response += f"• **Content Length**: {len(
|
104
|
+
response += f"• **Content Length**: {len(text)} characters\n"
|
73
105
|
|
74
106
|
if isinstance(result, str) and result:
|
75
107
|
response += f"\n**Server Response:**\n```\n{result}\n```"
|
@@ -86,47 +118,7 @@ async def sitebay_site_get_events(
|
|
86
118
|
after_datetime: Optional[str] = None,
|
87
119
|
limit: int = 20
|
88
120
|
) -> str:
|
89
|
-
""
|
90
|
-
Get recent events for a site (deployments, updates, restores, etc.).
|
91
|
-
|
92
|
-
Args:
|
93
|
-
fqdn: The fully qualified domain name of the site
|
94
|
-
after_datetime: Optional datetime to filter events after (ISO format)
|
95
|
-
limit: Maximum number of events to return (default: 20)
|
96
|
-
|
97
|
-
Returns:
|
98
|
-
Formatted list of site events
|
99
|
-
"""
|
100
|
-
try:
|
101
|
-
events = await client.get_site_events(fqdn, after_datetime)
|
102
|
-
|
103
|
-
if not events:
|
104
|
-
return f"No events found for {fqdn}."
|
105
|
-
|
106
|
-
# Limit the results
|
107
|
-
events = events[:limit]
|
108
|
-
|
109
|
-
result = f"**Recent Events for {fqdn}** (showing {len(events)} events):\n\n"
|
110
|
-
|
111
|
-
for event in events:
|
112
|
-
result += f"• **{event.get('event_type', 'Unknown Event')}**\n"
|
113
|
-
result += f" - Time: {event.get('created_at', 'Unknown')}\n"
|
114
|
-
result += f" - Status: {event.get('status', 'Unknown')}\n"
|
115
|
-
|
116
|
-
if event.get('description'):
|
117
|
-
result += f" - Description: {event.get('description')}\n"
|
118
|
-
|
119
|
-
if event.get('metadata'):
|
120
|
-
metadata = event.get('metadata') or {}
|
121
|
-
for key, value in metadata.items():
|
122
|
-
result += f" - {key.title()}: {value}\n"
|
123
|
-
|
124
|
-
result += "\n"
|
125
|
-
|
126
|
-
return result
|
127
|
-
|
128
|
-
except SiteBayError as e:
|
129
|
-
return f"Error getting events for {fqdn}: {str(e)}"
|
121
|
+
return "Site events are not available via schema endpoints."
|
130
122
|
|
131
123
|
|
132
124
|
async def sitebay_site_external_path_list(
|
@@ -142,26 +134,7 @@ async def sitebay_site_external_path_list(
|
|
142
134
|
Returns:
|
143
135
|
List of external path configurations
|
144
136
|
"""
|
145
|
-
|
146
|
-
paths = await client.list_external_paths(fqdn)
|
147
|
-
|
148
|
-
if not paths:
|
149
|
-
return f"No external paths configured for {fqdn}."
|
150
|
-
|
151
|
-
result = f"**External Paths for {fqdn}**:\n\n"
|
152
|
-
|
153
|
-
for path in paths:
|
154
|
-
result += f"• **Path**: {path.get('path', 'Unknown')}\n"
|
155
|
-
result += f" - Target URL: {path.get('target_url', 'Unknown')}\n"
|
156
|
-
result += f" - Status: {path.get('status', 'Unknown')}\n"
|
157
|
-
result += f" - Created: {path.get('created_at', 'Unknown')}\n"
|
158
|
-
result += f" - ID: {path.get('id', 'Unknown')}\n"
|
159
|
-
result += "\n"
|
160
|
-
|
161
|
-
return result
|
162
|
-
|
163
|
-
except SiteBayError as e:
|
164
|
-
return f"Error listing external paths for {fqdn}: {str(e)}"
|
137
|
+
return "External path tools are no longer supported."
|
165
138
|
|
166
139
|
|
167
140
|
async def sitebay_site_external_path_create(
|
@@ -183,33 +156,7 @@ async def sitebay_site_external_path_create(
|
|
183
156
|
Returns:
|
184
157
|
Success message with path details
|
185
158
|
"""
|
186
|
-
|
187
|
-
path_data = {
|
188
|
-
"path": path,
|
189
|
-
"target_url": target_url,
|
190
|
-
}
|
191
|
-
|
192
|
-
if description:
|
193
|
-
path_data["description"] = description
|
194
|
-
|
195
|
-
external_path = await client.create_external_path(fqdn, path_data)
|
196
|
-
|
197
|
-
result = f"✅ **External Path Created Successfully**\n\n"
|
198
|
-
result += f"• **Site**: {fqdn}\n"
|
199
|
-
result += f"• **Path**: {external_path.get('path')}\n"
|
200
|
-
result += f"• **Target URL**: {external_path.get('target_url')}\n"
|
201
|
-
result += f"• **Status**: {external_path.get('status')}\n"
|
202
|
-
result += f"• **ID**: {external_path.get('id')}\n"
|
203
|
-
|
204
|
-
if description:
|
205
|
-
result += f"• **Description**: {description}\n"
|
206
|
-
|
207
|
-
result += f"\n🔗 Your site path {fqdn}{path} now proxies to {target_url}"
|
208
|
-
|
209
|
-
return result
|
210
|
-
|
211
|
-
except SiteBayError as e:
|
212
|
-
return f"Error creating external path for {fqdn}: {str(e)}"
|
159
|
+
return "External path tools are no longer supported."
|
213
160
|
|
214
161
|
|
215
162
|
async def sitebay_site_external_path_update(
|
@@ -233,32 +180,7 @@ async def sitebay_site_external_path_update(
|
|
233
180
|
Returns:
|
234
181
|
Update confirmation message
|
235
182
|
"""
|
236
|
-
|
237
|
-
path_data = {}
|
238
|
-
|
239
|
-
if path:
|
240
|
-
path_data["path"] = path
|
241
|
-
if target_url:
|
242
|
-
path_data["target_url"] = target_url
|
243
|
-
if description:
|
244
|
-
path_data["description"] = description
|
245
|
-
|
246
|
-
if not path_data:
|
247
|
-
return "No updates specified. Please provide at least one field to update."
|
248
|
-
|
249
|
-
external_path = await client.update_external_path(fqdn, path_id, path_data)
|
250
|
-
|
251
|
-
result = f"✅ **External Path Updated Successfully**\n\n"
|
252
|
-
result += f"• **Site**: {fqdn}\n"
|
253
|
-
result += f"• **Path**: {external_path.get('path')}\n"
|
254
|
-
result += f"• **Target URL**: {external_path.get('target_url')}\n"
|
255
|
-
result += f"• **Status**: {external_path.get('status')}\n"
|
256
|
-
result += f"• **ID**: {external_path.get('id')}\n"
|
257
|
-
|
258
|
-
return result
|
259
|
-
|
260
|
-
except SiteBayError as e:
|
261
|
-
return f"Error updating external path for {fqdn}: {str(e)}"
|
183
|
+
return "External path tools are no longer supported."
|
262
184
|
|
263
185
|
|
264
186
|
async def sitebay_site_external_path_delete(
|
@@ -276,10 +198,4 @@ async def sitebay_site_external_path_delete(
|
|
276
198
|
Returns:
|
277
199
|
Deletion confirmation message
|
278
200
|
"""
|
279
|
-
|
280
|
-
await client.delete_external_path(fqdn, path_id)
|
281
|
-
|
282
|
-
return f"✅ **External Path Deleted Successfully**\n\nExternal path {path_id} has been removed from {fqdn}."
|
283
|
-
|
284
|
-
except SiteBayError as e:
|
285
|
-
return f"Error deleting external path for {fqdn}: {str(e)}"
|
201
|
+
return "External path tools are no longer supported."
|
sitebay_mcp/tools/sites.py
CHANGED
@@ -21,9 +21,9 @@ async def sitebay_list_sites(
|
|
21
21
|
Formatted string with site details
|
22
22
|
"""
|
23
23
|
try:
|
24
|
-
|
25
|
-
|
26
|
-
|
24
|
+
# Normalize team_id to string (UUID4 expected by API)
|
25
|
+
if team_id is not None:
|
26
|
+
team_id = str(team_id)
|
27
27
|
|
28
28
|
sites = await client.list_sites(team_id=team_id)
|
29
29
|
|
@@ -102,61 +102,76 @@ async def sitebay_get_site(
|
|
102
102
|
|
103
103
|
async def sitebay_create_site(
|
104
104
|
client: SiteBayClient,
|
105
|
+
team_id: str,
|
105
106
|
fqdn: str,
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
107
|
+
wordpress_blog_name: str,
|
108
|
+
wordpress_first_name: str,
|
109
|
+
wordpress_last_name: str,
|
110
|
+
wordpress_email: str,
|
111
|
+
wordpress_username: str,
|
112
|
+
wordpress_password: str,
|
113
|
+
git_url: Optional[str] = None,
|
114
|
+
ready_made_site_name: Optional[str] = None,
|
115
|
+
is_free: Optional[bool] = None,
|
113
116
|
) -> str:
|
114
117
|
"""
|
115
|
-
Create a new WordPress site.
|
118
|
+
Create a new WordPress site (SiteLiveCreate schema).
|
116
119
|
|
117
120
|
Args:
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
121
|
+
team_id: Team UUID
|
122
|
+
fqdn: New site domain
|
123
|
+
wordpress_blog_name: Blog/site title
|
124
|
+
wordpress_first_name: Admin first name
|
125
|
+
wordpress_last_name: Admin last name
|
126
|
+
wordpress_email: Admin email
|
127
|
+
wordpress_username: Admin username
|
128
|
+
wordpress_password: Admin password
|
129
|
+
git_url: Optional repository URL
|
130
|
+
ready_made_site_name: Optional ready-made site name
|
131
|
+
is_free: Optional free plan flag
|
126
132
|
|
127
133
|
Returns:
|
128
134
|
Formatted string with new site details
|
129
135
|
"""
|
130
136
|
try:
|
131
|
-
site_data = {
|
137
|
+
site_data: Dict[str, Any] = {
|
138
|
+
"team_id": team_id,
|
132
139
|
"fqdn": fqdn,
|
133
|
-
"
|
134
|
-
"
|
135
|
-
"
|
136
|
-
"
|
140
|
+
"wordpress_blog_name": wordpress_blog_name,
|
141
|
+
"wordpress_first_name": wordpress_first_name,
|
142
|
+
"wordpress_last_name": wordpress_last_name,
|
143
|
+
"wordpress_email": wordpress_email,
|
144
|
+
"wordpress_username": wordpress_username,
|
145
|
+
"wordpress_password": wordpress_password,
|
137
146
|
}
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
147
|
+
if git_url:
|
148
|
+
site_data["git_url"] = git_url
|
149
|
+
if ready_made_site_name:
|
150
|
+
site_data["ready_made_site_name"] = ready_made_site_name
|
151
|
+
if is_free is not None:
|
152
|
+
site_data["is_free"] = is_free
|
153
|
+
|
146
154
|
site = await client.create_site(site_data)
|
147
|
-
|
155
|
+
|
148
156
|
result = f"✅ **Site Created Successfully!**\n\n"
|
149
157
|
result += f"• **Domain**: {site.get('fqdn')}\n"
|
150
158
|
result += f"• **Status**: {site.get('status')}\n"
|
151
|
-
|
159
|
+
if site.get('region_name'):
|
160
|
+
result += f"• **Region**: {site.get('region_name')}\n"
|
152
161
|
result += f"• **Site URL**: {site.get('site_url')}\n"
|
153
162
|
result += f"• **Admin URL**: {site.get('admin_url')}\n"
|
154
|
-
result += f"• **
|
155
|
-
result += f"• **
|
156
|
-
|
157
|
-
|
163
|
+
result += f"• **Admin Username**: {wordpress_username}\n"
|
164
|
+
result += f"• **Admin Email**: {wordpress_email}\n"
|
165
|
+
if git_url:
|
166
|
+
result += f"• **Git URL**: {git_url}\n"
|
167
|
+
if ready_made_site_name:
|
168
|
+
result += f"• **Ready-made**: {ready_made_site_name}\n"
|
169
|
+
if is_free is not None:
|
170
|
+
result += f"• **Plan**: {'Free' if is_free else 'Paid'}\n"
|
171
|
+
result += "\n🚀 Your WordPress site is being deployed and will be ready shortly!"
|
172
|
+
|
158
173
|
return result
|
159
|
-
|
174
|
+
|
160
175
|
except SiteBayError as e:
|
161
176
|
return f"Error creating site: {str(e)}"
|
162
177
|
|
@@ -164,42 +179,46 @@ async def sitebay_create_site(
|
|
164
179
|
async def sitebay_update_site(
|
165
180
|
client: SiteBayClient,
|
166
181
|
fqdn: str,
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
182
|
+
cf_dev_mode_enabled: Optional[bool] = None,
|
183
|
+
new_fqdn: Optional[str] = None,
|
184
|
+
git_url: Optional[str] = None,
|
185
|
+
http_auth_enabled: Optional[bool] = None,
|
186
|
+
team_id: Optional[str] = None,
|
187
|
+
is_free: Optional[bool] = None,
|
172
188
|
) -> str:
|
173
189
|
"""
|
174
|
-
Update an existing
|
190
|
+
Update an existing SiteBay site configuration.
|
175
191
|
|
176
192
|
Args:
|
177
193
|
fqdn: The fully qualified domain name of the site to update
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
194
|
+
cf_dev_mode_enabled: Enable/disable Cloudflare dev mode
|
195
|
+
new_fqdn: New domain for the site
|
196
|
+
git_url: Git repository URL
|
197
|
+
http_auth_enabled: Enable/disable HTTP basic auth
|
198
|
+
team_id: Move site to a different team
|
199
|
+
is_free: Toggle free plan flag
|
183
200
|
|
184
201
|
Returns:
|
185
202
|
Formatted string with update confirmation
|
186
203
|
"""
|
187
204
|
try:
|
188
|
-
site_data = {}
|
205
|
+
site_data: Dict[str, Any] = {}
|
189
206
|
|
190
|
-
if
|
191
|
-
site_data["
|
192
|
-
if
|
193
|
-
site_data["
|
194
|
-
if
|
195
|
-
site_data["
|
196
|
-
if
|
197
|
-
site_data["
|
198
|
-
if
|
199
|
-
site_data["
|
207
|
+
if cf_dev_mode_enabled is not None:
|
208
|
+
site_data["cf_dev_mode_enabled"] = cf_dev_mode_enabled
|
209
|
+
if new_fqdn:
|
210
|
+
site_data["new_fqdn"] = new_fqdn
|
211
|
+
if git_url:
|
212
|
+
site_data["git_url"] = git_url
|
213
|
+
if http_auth_enabled is not None:
|
214
|
+
site_data["http_auth_enabled"] = http_auth_enabled
|
215
|
+
if team_id:
|
216
|
+
site_data["team_id"] = team_id
|
217
|
+
if is_free is not None:
|
218
|
+
site_data["is_free"] = is_free
|
200
219
|
|
201
220
|
if not site_data:
|
202
|
-
return "No updates specified.
|
221
|
+
return "No updates specified. Provide at least one supported field."
|
203
222
|
|
204
223
|
site = await client.update_site(fqdn, site_data)
|
205
224
|
|
@@ -207,16 +226,18 @@ async def sitebay_update_site(
|
|
207
226
|
result += f"• **Domain**: {site.get('fqdn')}\n"
|
208
227
|
result += f"• **Status**: {site.get('status')}\n"
|
209
228
|
|
210
|
-
if
|
211
|
-
result += f"• **
|
212
|
-
if
|
213
|
-
result += f"• **
|
214
|
-
if
|
215
|
-
result += f"• **
|
216
|
-
if
|
217
|
-
result += f"• **
|
218
|
-
if
|
219
|
-
result += f"• **
|
229
|
+
if cf_dev_mode_enabled is not None:
|
230
|
+
result += f"• **Cloudflare Dev Mode**: {'Enabled' if cf_dev_mode_enabled else 'Disabled'}\n"
|
231
|
+
if new_fqdn:
|
232
|
+
result += f"• **New Domain**: {new_fqdn}\n"
|
233
|
+
if git_url:
|
234
|
+
result += f"• **Git URL**: {git_url}\n"
|
235
|
+
if http_auth_enabled is not None:
|
236
|
+
result += f"• **HTTP Auth**: {'Enabled' if http_auth_enabled else 'Disabled'}\n"
|
237
|
+
if team_id:
|
238
|
+
result += f"• **Team ID**: Moved to {team_id}\n"
|
239
|
+
if is_free is not None:
|
240
|
+
result += f"• **Plan**: {'Free' if is_free else 'Paid'}\n"
|
220
241
|
|
221
242
|
return result
|
222
243
|
|
@@ -227,30 +248,16 @@ async def sitebay_update_site(
|
|
227
248
|
async def sitebay_delete_site(
|
228
249
|
client: SiteBayClient,
|
229
250
|
fqdn: str,
|
230
|
-
confirm: bool = False
|
231
251
|
) -> str:
|
232
252
|
"""
|
233
253
|
Delete a WordPress site permanently.
|
234
254
|
|
235
255
|
Args:
|
236
256
|
fqdn: The fully qualified domain name of the site to delete
|
237
|
-
confirm: Must be True to actually delete the site (safety check)
|
238
257
|
|
239
258
|
Returns:
|
240
259
|
Confirmation message or error
|
241
260
|
"""
|
242
|
-
if not confirm:
|
243
|
-
return (
|
244
|
-
f"⚠️ **CONFIRMATION REQUIRED**\n\n"
|
245
|
-
f"You are about to permanently delete the site: **{fqdn}**\n\n"
|
246
|
-
f"This action will:\n"
|
247
|
-
f"• Delete all website files and content\n"
|
248
|
-
f"• Delete the database and all data\n"
|
249
|
-
f"• Remove any staging sites\n"
|
250
|
-
f"• Cannot be undone\n\n"
|
251
|
-
f"To proceed with deletion, call this function again with confirm=True"
|
252
|
-
)
|
253
|
-
|
254
261
|
try:
|
255
262
|
await client.delete_site(fqdn)
|
256
263
|
|
@@ -260,4 +267,4 @@ async def sitebay_delete_site(
|
|
260
267
|
)
|
261
268
|
|
262
269
|
except SiteBayError as e:
|
263
|
-
return f"Error deleting site: {str(e)}"
|
270
|
+
return f"Error deleting site: {str(e)}"
|