@suzko/mcp-server 0.1.0
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.
- package/CHANGELOG.md +32 -0
- package/GUIDE.md +952 -0
- package/LICENSE +21 -0
- package/README.md +171 -0
- package/dist/bin/mcp-server.d.ts +11 -0
- package/dist/bin/mcp-server.js +108 -0
- package/dist/src/auth.d.ts +14 -0
- package/dist/src/auth.js +140 -0
- package/dist/src/client.d.ts +18 -0
- package/dist/src/client.js +78 -0
- package/dist/src/index.d.ts +8 -0
- package/dist/src/index.js +44 -0
- package/dist/src/prompts/index.d.ts +5 -0
- package/dist/src/prompts/index.js +87 -0
- package/dist/src/resources/index.d.ts +7 -0
- package/dist/src/resources/index.js +86 -0
- package/dist/src/security.d.ts +18 -0
- package/dist/src/security.js +108 -0
- package/dist/src/tools/account.d.ts +3 -0
- package/dist/src/tools/account.js +254 -0
- package/dist/src/tools/deploy.d.ts +3 -0
- package/dist/src/tools/deploy.js +468 -0
- package/dist/src/tools/dns.d.ts +3 -0
- package/dist/src/tools/dns.js +313 -0
- package/dist/src/tools/domains.d.ts +3 -0
- package/dist/src/tools/domains.js +499 -0
- package/dist/src/tools/server-admin.d.ts +3 -0
- package/dist/src/tools/server-admin.js +738 -0
- package/dist/src/tools/services.d.ts +3 -0
- package/dist/src/tools/services.js +181 -0
- package/dist/src/tools/support.d.ts +3 -0
- package/dist/src/tools/support.js +259 -0
- package/package.json +57 -0
|
@@ -0,0 +1,468 @@
|
|
|
1
|
+
import { z } from "zod";
|
|
2
|
+
import { assertSafeSubdomain } from "../security.js";
|
|
3
|
+
// Pending-confirmation store for two-step destructive ops. 30-minute TTL, single-use UUID tokens.
|
|
4
|
+
const pendingConfirmations = new Map();
|
|
5
|
+
const CONFIRM_TTL_MS = 30 * 60 * 1000;
|
|
6
|
+
function createConfirmation(params) {
|
|
7
|
+
const id = crypto.randomUUID();
|
|
8
|
+
pendingConfirmations.set(id, {
|
|
9
|
+
params,
|
|
10
|
+
expiresAt: Date.now() + CONFIRM_TTL_MS,
|
|
11
|
+
});
|
|
12
|
+
return id;
|
|
13
|
+
}
|
|
14
|
+
function consumeConfirmation(id) {
|
|
15
|
+
const entry = pendingConfirmations.get(id);
|
|
16
|
+
if (!entry)
|
|
17
|
+
return null;
|
|
18
|
+
pendingConfirmations.delete(id);
|
|
19
|
+
if (Date.now() > entry.expiresAt)
|
|
20
|
+
return null;
|
|
21
|
+
return entry.params;
|
|
22
|
+
}
|
|
23
|
+
// Periodically clean expired confirmations
|
|
24
|
+
setInterval(() => {
|
|
25
|
+
const now = Date.now();
|
|
26
|
+
for (const [id, entry] of pendingConfirmations) {
|
|
27
|
+
if (now > entry.expiresAt)
|
|
28
|
+
pendingConfirmations.delete(id);
|
|
29
|
+
}
|
|
30
|
+
}, 60_000);
|
|
31
|
+
export function registerDeployTools(server, client) {
|
|
32
|
+
// 1. List deploy templates
|
|
33
|
+
server.tool("list_deploy_templates", "List available deployment templates (databases, runtimes, services). Optionally filter by category.", {
|
|
34
|
+
category: z
|
|
35
|
+
.string()
|
|
36
|
+
.optional()
|
|
37
|
+
.describe("Filter templates by category (e.g. 'database', 'runtime', 'cache')"),
|
|
38
|
+
}, async ({ category }) => {
|
|
39
|
+
const params = {};
|
|
40
|
+
if (category)
|
|
41
|
+
params.category = category;
|
|
42
|
+
const res = await client.get("/api/deploy/templates", params);
|
|
43
|
+
if (!res.success) {
|
|
44
|
+
return {
|
|
45
|
+
content: [{ type: "text", text: `Error: ${res.error}` }],
|
|
46
|
+
isError: true,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const templates = res.data?.templates ?? [];
|
|
50
|
+
if (templates.length === 0) {
|
|
51
|
+
return {
|
|
52
|
+
content: [
|
|
53
|
+
{
|
|
54
|
+
type: "text",
|
|
55
|
+
text: category
|
|
56
|
+
? `No templates found for category "${category}".`
|
|
57
|
+
: "No deployment templates available.",
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
const lines = templates.map((t) => `• **${t.name}** (${t.id})${t.category ? ` [${t.category}]` : ""}${t.description ? ` — ${t.description}` : ""}`);
|
|
63
|
+
return {
|
|
64
|
+
content: [
|
|
65
|
+
{
|
|
66
|
+
type: "text",
|
|
67
|
+
text: `**Available Templates (${templates.length}):**\n\n${lines.join("\n")}`,
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
};
|
|
71
|
+
});
|
|
72
|
+
// 2. List deploy projects
|
|
73
|
+
server.tool("list_deploy_projects", "List all of the user's deployed projects with their status and URLs.", {}, async () => {
|
|
74
|
+
const res = await client.get("/api/deploy/projects");
|
|
75
|
+
if (!res.success) {
|
|
76
|
+
return {
|
|
77
|
+
content: [{ type: "text", text: `Error: ${res.error}` }],
|
|
78
|
+
isError: true,
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
const projects = res.data?.projects ?? [];
|
|
82
|
+
if (projects.length === 0) {
|
|
83
|
+
return {
|
|
84
|
+
content: [
|
|
85
|
+
{
|
|
86
|
+
type: "text",
|
|
87
|
+
text: "No deploy projects found. Use `create_deploy_project` to create one.",
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
const lines = projects.map((p) => `• **${p.name}** (${p.id}) — Status: ${p.status}${p.url ? ` | URL: ${p.url}` : ""}${p.tier ? ` | Tier: ${p.tier}` : ""}`);
|
|
93
|
+
return {
|
|
94
|
+
content: [
|
|
95
|
+
{
|
|
96
|
+
type: "text",
|
|
97
|
+
text: `**Your Projects (${projects.length}):**\n\n${lines.join("\n")}`,
|
|
98
|
+
},
|
|
99
|
+
],
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
// 3. Create deploy project (with confirmation)
|
|
103
|
+
server.tool("create_deploy_project", "Create a new deployment project. Returns a confirmation preview — use confirm_deploy_project to finalize. This may incur costs depending on the tier.", {
|
|
104
|
+
name: z.string().describe("Project name"),
|
|
105
|
+
templateId: z.string().describe("Template ID to deploy (from list_deploy_templates)"),
|
|
106
|
+
tier: z
|
|
107
|
+
.enum(["nano", "micro", "small", "medium", "large", "xlarge"])
|
|
108
|
+
.describe("Resource tier: nano(256MB), micro(512MB), small(1GB), medium(2GB), large(4GB), xlarge(8GB)"),
|
|
109
|
+
stackId: z
|
|
110
|
+
.string()
|
|
111
|
+
.optional()
|
|
112
|
+
.describe("Stack template ID for multi-service deployments"),
|
|
113
|
+
subdomain: z
|
|
114
|
+
.string()
|
|
115
|
+
.optional()
|
|
116
|
+
.describe("Custom subdomain for the project"),
|
|
117
|
+
envVars: z
|
|
118
|
+
.record(z.string())
|
|
119
|
+
.optional()
|
|
120
|
+
.describe("Environment variables to set on the project"),
|
|
121
|
+
}, async ({ name, templateId, tier, stackId, subdomain, envVars }) => {
|
|
122
|
+
if (subdomain) {
|
|
123
|
+
try {
|
|
124
|
+
assertSafeSubdomain(subdomain);
|
|
125
|
+
}
|
|
126
|
+
catch (e) {
|
|
127
|
+
return {
|
|
128
|
+
content: [{ type: "text", text: e instanceof Error ? e.message : String(e) }],
|
|
129
|
+
isError: true,
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
const params = {
|
|
134
|
+
name,
|
|
135
|
+
templateId,
|
|
136
|
+
tier,
|
|
137
|
+
};
|
|
138
|
+
if (stackId)
|
|
139
|
+
params.stackId = stackId;
|
|
140
|
+
if (subdomain)
|
|
141
|
+
params.subdomain = subdomain;
|
|
142
|
+
if (envVars)
|
|
143
|
+
params.envVars = envVars;
|
|
144
|
+
const confirmationId = createConfirmation(params);
|
|
145
|
+
const tierDescriptions = {
|
|
146
|
+
nano: "256MB RAM",
|
|
147
|
+
micro: "512MB RAM",
|
|
148
|
+
small: "1GB RAM",
|
|
149
|
+
medium: "2GB RAM",
|
|
150
|
+
large: "4GB RAM",
|
|
151
|
+
xlarge: "8GB RAM",
|
|
152
|
+
};
|
|
153
|
+
const preview = [
|
|
154
|
+
`**Create Deploy Project — Confirmation Required**`,
|
|
155
|
+
``,
|
|
156
|
+
`| Setting | Value |`,
|
|
157
|
+
`|---------|-------|`,
|
|
158
|
+
`| Name | ${name} |`,
|
|
159
|
+
`| Template | ${templateId} |`,
|
|
160
|
+
`| Tier | ${tier} (${tierDescriptions[tier] ?? tier}) |`,
|
|
161
|
+
stackId ? `| Stack | ${stackId} |` : null,
|
|
162
|
+
subdomain ? `| Subdomain | ${subdomain} |` : null,
|
|
163
|
+
envVars
|
|
164
|
+
? `| Env Vars | ${Object.keys(envVars).length} variable(s) |`
|
|
165
|
+
: null,
|
|
166
|
+
``,
|
|
167
|
+
`⚠️ This will create a new deployment that may incur costs based on your subscription plan.`,
|
|
168
|
+
``,
|
|
169
|
+
`To confirm, call \`confirm_deploy_project\` with confirmation ID:`,
|
|
170
|
+
`\`${confirmationId}\``,
|
|
171
|
+
``,
|
|
172
|
+
`This confirmation expires in 5 minutes.`,
|
|
173
|
+
]
|
|
174
|
+
.filter(Boolean)
|
|
175
|
+
.join("\n");
|
|
176
|
+
return {
|
|
177
|
+
content: [{ type: "text", text: preview }],
|
|
178
|
+
};
|
|
179
|
+
});
|
|
180
|
+
// 3b. Confirm deploy project creation
|
|
181
|
+
server.tool("confirm_deploy_project", "Confirm and execute a pending deploy project creation. Use the confirmation ID returned by create_deploy_project.", {
|
|
182
|
+
confirmationId: z
|
|
183
|
+
.string()
|
|
184
|
+
.describe("The confirmation ID from create_deploy_project"),
|
|
185
|
+
}, async ({ confirmationId }) => {
|
|
186
|
+
const params = consumeConfirmation(confirmationId);
|
|
187
|
+
if (!params) {
|
|
188
|
+
return {
|
|
189
|
+
content: [
|
|
190
|
+
{
|
|
191
|
+
type: "text",
|
|
192
|
+
text: "Confirmation not found or expired. Please run `create_deploy_project` again.",
|
|
193
|
+
},
|
|
194
|
+
],
|
|
195
|
+
isError: true,
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
const res = await client.post("/api/deploy/projects", params);
|
|
199
|
+
if (!res.success) {
|
|
200
|
+
return {
|
|
201
|
+
content: [
|
|
202
|
+
{
|
|
203
|
+
type: "text",
|
|
204
|
+
text: `Failed to create project: ${res.error}`,
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
isError: true,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
const p = res.data?.project;
|
|
211
|
+
return {
|
|
212
|
+
content: [
|
|
213
|
+
{
|
|
214
|
+
type: "text",
|
|
215
|
+
text: p
|
|
216
|
+
? `✅ Project **${p.name}** created successfully!\n\n- ID: ${p.id}\n- Status: ${p.status}${p.url ? `\n- URL: ${p.url}` : ""}`
|
|
217
|
+
: "Project created successfully.",
|
|
218
|
+
},
|
|
219
|
+
],
|
|
220
|
+
};
|
|
221
|
+
});
|
|
222
|
+
// 4. Get deploy project details
|
|
223
|
+
server.tool("get_deploy_project", "Get full details of a specific deploy project including status, URL, services, domains, and resource usage.", {
|
|
224
|
+
projectId: z.string().describe("The project ID"),
|
|
225
|
+
}, async ({ projectId }) => {
|
|
226
|
+
const res = await client.get(`/api/deploy/projects/${projectId}`);
|
|
227
|
+
if (!res.success) {
|
|
228
|
+
return {
|
|
229
|
+
content: [{ type: "text", text: `Error: ${res.error}` }],
|
|
230
|
+
isError: true,
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
return {
|
|
234
|
+
content: [
|
|
235
|
+
{
|
|
236
|
+
type: "text",
|
|
237
|
+
text: `**Project Details:**\n\n\`\`\`json\n${JSON.stringify(res.data?.project ?? res.data, null, 2)}\n\`\`\``,
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
};
|
|
241
|
+
});
|
|
242
|
+
// 5. Control deploy project (start, stop, restart, delete — delete is two-step)
|
|
243
|
+
server.tool("control_deploy_project", "Control a deploy project: start, stop, restart, or begin delete. 'delete' returns a confirmation ID — use confirm_delete_deploy_project to actually destroy the project.", {
|
|
244
|
+
projectId: z.string().min(1).max(128).describe("The project ID"),
|
|
245
|
+
action: z
|
|
246
|
+
.enum(["start", "stop", "restart", "delete"])
|
|
247
|
+
.describe("Action to perform on the project"),
|
|
248
|
+
}, async ({ projectId, action }) => {
|
|
249
|
+
if (action === "delete") {
|
|
250
|
+
const confirmationId = createConfirmation({ projectId, op: "delete_project" });
|
|
251
|
+
return {
|
|
252
|
+
content: [
|
|
253
|
+
{
|
|
254
|
+
type: "text",
|
|
255
|
+
text: `**Delete Deploy Project — confirmation required**\n\n` +
|
|
256
|
+
`| Field | Value |\n|-------|-------|\n| Project ID | ${projectId} |\n\n` +
|
|
257
|
+
`⚠️ This will permanently destroy the project, all deployments, attached volumes, and managed domains. ` +
|
|
258
|
+
`It cannot be undone.\n\nCall \`confirm_delete_deploy_project\` with:\n\n\`${confirmationId}\`\n\nExpires in 30 minutes.`,
|
|
259
|
+
},
|
|
260
|
+
],
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
// start, stop, restart map to PUT with status or POST to control endpoint
|
|
264
|
+
const res = await client.put(`/api/deploy/projects/${projectId}`, { action });
|
|
265
|
+
if (!res.success) {
|
|
266
|
+
return {
|
|
267
|
+
content: [{ type: "text", text: `Error: ${res.error}` }],
|
|
268
|
+
isError: true,
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
const status = res.data?.project?.status ?? action;
|
|
272
|
+
return {
|
|
273
|
+
content: [
|
|
274
|
+
{
|
|
275
|
+
type: "text",
|
|
276
|
+
text: `Project ${projectId}: ${action} executed. Current status: ${status}`,
|
|
277
|
+
},
|
|
278
|
+
],
|
|
279
|
+
};
|
|
280
|
+
});
|
|
281
|
+
// 5b. Confirm deploy project deletion
|
|
282
|
+
server.tool("confirm_delete_deploy_project", "Confirm and execute a pending deploy-project deletion. Pass the confirmation ID returned by control_deploy_project(delete).", {
|
|
283
|
+
confirmationId: z.string().min(1).max(128).describe("Confirmation ID from control_deploy_project"),
|
|
284
|
+
}, async ({ confirmationId }) => {
|
|
285
|
+
const params = consumeConfirmation(confirmationId);
|
|
286
|
+
if (!params || params.op !== "delete_project" || typeof params.projectId !== "string") {
|
|
287
|
+
return {
|
|
288
|
+
content: [
|
|
289
|
+
{
|
|
290
|
+
type: "text",
|
|
291
|
+
text: "Confirmation not found or expired. Call control_deploy_project with action='delete' again.",
|
|
292
|
+
},
|
|
293
|
+
],
|
|
294
|
+
isError: true,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
const res = await client.del(`/api/deploy/projects/${params.projectId}`);
|
|
298
|
+
if (!res.success) {
|
|
299
|
+
return {
|
|
300
|
+
content: [{ type: "text", text: `Error: ${res.error}` }],
|
|
301
|
+
isError: true,
|
|
302
|
+
};
|
|
303
|
+
}
|
|
304
|
+
return {
|
|
305
|
+
content: [
|
|
306
|
+
{
|
|
307
|
+
type: "text",
|
|
308
|
+
text: `✅ Project ${params.projectId} has been deleted.`,
|
|
309
|
+
},
|
|
310
|
+
],
|
|
311
|
+
};
|
|
312
|
+
});
|
|
313
|
+
// 6. Get deploy logs
|
|
314
|
+
server.tool("get_deploy_logs", "Get runtime logs from a deploy project's container.", {
|
|
315
|
+
projectId: z.string().describe("The project ID"),
|
|
316
|
+
tail: z
|
|
317
|
+
.number()
|
|
318
|
+
.optional()
|
|
319
|
+
.default(100)
|
|
320
|
+
.describe("Number of log lines to return (default: 100)"),
|
|
321
|
+
since: z
|
|
322
|
+
.string()
|
|
323
|
+
.optional()
|
|
324
|
+
.describe("Return logs since this timestamp (ISO 8601)"),
|
|
325
|
+
}, async ({ projectId, tail, since }) => {
|
|
326
|
+
const params = {};
|
|
327
|
+
if (tail)
|
|
328
|
+
params.tail = String(tail);
|
|
329
|
+
if (since)
|
|
330
|
+
params.since = since;
|
|
331
|
+
const res = await client.get(`/api/deploy/projects/${projectId}/logs`, params);
|
|
332
|
+
if (!res.success) {
|
|
333
|
+
return {
|
|
334
|
+
content: [{ type: "text", text: `Error: ${res.error}` }],
|
|
335
|
+
isError: true,
|
|
336
|
+
};
|
|
337
|
+
}
|
|
338
|
+
const logs = res.data?.logs ?? "(no logs)";
|
|
339
|
+
return {
|
|
340
|
+
content: [
|
|
341
|
+
{
|
|
342
|
+
type: "text",
|
|
343
|
+
text: `**Logs for ${projectId}:**\n\n\`\`\`\n${logs}\n\`\`\``,
|
|
344
|
+
},
|
|
345
|
+
],
|
|
346
|
+
};
|
|
347
|
+
});
|
|
348
|
+
// 7. Get deploy build logs
|
|
349
|
+
server.tool("get_deploy_build_logs", "Get build output/logs from the last deployment build of a project.", {
|
|
350
|
+
projectId: z.string().describe("The project ID"),
|
|
351
|
+
}, async ({ projectId }) => {
|
|
352
|
+
const res = await client.get(`/api/deploy/projects/${projectId}/build-logs`);
|
|
353
|
+
if (!res.success) {
|
|
354
|
+
return {
|
|
355
|
+
content: [{ type: "text", text: `Error: ${res.error}` }],
|
|
356
|
+
isError: true,
|
|
357
|
+
};
|
|
358
|
+
}
|
|
359
|
+
const logs = res.data?.logs ?? "(no build logs)";
|
|
360
|
+
return {
|
|
361
|
+
content: [
|
|
362
|
+
{
|
|
363
|
+
type: "text",
|
|
364
|
+
text: `**Build Logs for ${projectId}:**${res.data?.buildId ? ` (build: ${res.data.buildId})` : ""}\n\n\`\`\`\n${logs}\n\`\`\``,
|
|
365
|
+
},
|
|
366
|
+
],
|
|
367
|
+
};
|
|
368
|
+
});
|
|
369
|
+
// 8. Redeploy project
|
|
370
|
+
server.tool("redeploy_project", "Trigger a redeployment of a deploy project, rebuilding and restarting the container.", {
|
|
371
|
+
projectId: z.string().describe("The project ID"),
|
|
372
|
+
}, async ({ projectId }) => {
|
|
373
|
+
const res = await client.post(`/api/deploy/projects/${projectId}/redeploy`);
|
|
374
|
+
if (!res.success) {
|
|
375
|
+
return {
|
|
376
|
+
content: [{ type: "text", text: `Error: ${res.error}` }],
|
|
377
|
+
isError: true,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
const d = res.data?.deployment;
|
|
381
|
+
return {
|
|
382
|
+
content: [
|
|
383
|
+
{
|
|
384
|
+
type: "text",
|
|
385
|
+
text: d
|
|
386
|
+
? `Redeployment triggered for ${projectId}.\n\n- Deployment ID: ${d.id}\n- Status: ${d.status}`
|
|
387
|
+
: `Redeployment triggered for ${projectId}.`,
|
|
388
|
+
},
|
|
389
|
+
],
|
|
390
|
+
};
|
|
391
|
+
});
|
|
392
|
+
// 9. Set environment variables
|
|
393
|
+
server.tool("set_deploy_env", "Set environment variables on a deploy project. Existing variables with the same key will be overwritten.", {
|
|
394
|
+
projectId: z.string().describe("The project ID"),
|
|
395
|
+
envVars: z
|
|
396
|
+
.record(z.string())
|
|
397
|
+
.describe("Key-value pairs of environment variables to set (e.g. { \"DATABASE_URL\": \"postgres://...\" })"),
|
|
398
|
+
}, async ({ projectId, envVars }) => {
|
|
399
|
+
const res = await client.post(`/api/deploy/projects/${projectId}/env`, { envVars });
|
|
400
|
+
if (!res.success) {
|
|
401
|
+
return {
|
|
402
|
+
content: [{ type: "text", text: `Error: ${res.error}` }],
|
|
403
|
+
isError: true,
|
|
404
|
+
};
|
|
405
|
+
}
|
|
406
|
+
const keys = Object.keys(envVars);
|
|
407
|
+
return {
|
|
408
|
+
content: [
|
|
409
|
+
{
|
|
410
|
+
type: "text",
|
|
411
|
+
text: `Environment variables set on ${projectId}: ${keys.map((k) => `\`${k}\``).join(", ")}.\n\nNote: You may need to redeploy for changes to take effect.`,
|
|
412
|
+
},
|
|
413
|
+
],
|
|
414
|
+
};
|
|
415
|
+
});
|
|
416
|
+
// 10. Check subdomain availability
|
|
417
|
+
server.tool("check_subdomain", "Check if a subdomain is available for use with a deploy project.", {
|
|
418
|
+
subdomain: z
|
|
419
|
+
.string()
|
|
420
|
+
.describe("The subdomain to check (e.g. 'my-app')"),
|
|
421
|
+
}, async ({ subdomain }) => {
|
|
422
|
+
try {
|
|
423
|
+
assertSafeSubdomain(subdomain);
|
|
424
|
+
}
|
|
425
|
+
catch (e) {
|
|
426
|
+
return {
|
|
427
|
+
content: [{ type: "text", text: e instanceof Error ? e.message : String(e) }],
|
|
428
|
+
isError: true,
|
|
429
|
+
};
|
|
430
|
+
}
|
|
431
|
+
const res = await client.get("/api/deploy/subdomain-check", { subdomain });
|
|
432
|
+
if (!res.success) {
|
|
433
|
+
return {
|
|
434
|
+
content: [{ type: "text", text: `Error: ${res.error}` }],
|
|
435
|
+
isError: true,
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
const available = res.data?.available;
|
|
439
|
+
return {
|
|
440
|
+
content: [
|
|
441
|
+
{
|
|
442
|
+
type: "text",
|
|
443
|
+
text: available
|
|
444
|
+
? `✅ Subdomain **${subdomain}** is available.`
|
|
445
|
+
: `❌ Subdomain **${subdomain}** is already taken. Try a different name.`,
|
|
446
|
+
},
|
|
447
|
+
],
|
|
448
|
+
};
|
|
449
|
+
});
|
|
450
|
+
// 11. Get deploy usage
|
|
451
|
+
server.tool("get_deploy_usage", "Get the user's deploy usage statistics including project counts, resource consumption, and plan limits.", {}, async () => {
|
|
452
|
+
const res = await client.get("/api/deploy/usage");
|
|
453
|
+
if (!res.success) {
|
|
454
|
+
return {
|
|
455
|
+
content: [{ type: "text", text: `Error: ${res.error}` }],
|
|
456
|
+
isError: true,
|
|
457
|
+
};
|
|
458
|
+
}
|
|
459
|
+
return {
|
|
460
|
+
content: [
|
|
461
|
+
{
|
|
462
|
+
type: "text",
|
|
463
|
+
text: `**Deploy Usage:**\n\n\`\`\`json\n${JSON.stringify(res.data, null, 2)}\n\`\`\``,
|
|
464
|
+
},
|
|
465
|
+
],
|
|
466
|
+
};
|
|
467
|
+
});
|
|
468
|
+
}
|