@roxyon/mcp 0.8.0 → 0.10.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/dist/{chunk-W6X4YMV5.js → chunk-BNRJ2C63.js} +254 -141
- package/dist/chunk-BNRJ2C63.js.map +1 -0
- package/dist/http-entry.js +5 -2
- package/dist/http-entry.js.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +3 -3
- package/resources/deploy.md +1 -1
- package/dist/chunk-W6X4YMV5.js.map +0 -1
|
@@ -4,10 +4,7 @@
|
|
|
4
4
|
import { createRequire } from "module";
|
|
5
5
|
import { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
6
6
|
|
|
7
|
-
// src/
|
|
8
|
-
import { readFile } from "fs/promises";
|
|
9
|
-
import { join, resolve } from "path";
|
|
10
|
-
import { BAAS_MAX_BATCH, BAAS_MAX_PAGE, BaasDataApi, parseEnv } from "@roxyon/api-client";
|
|
7
|
+
// src/account-tools.ts
|
|
11
8
|
import { z } from "zod";
|
|
12
9
|
|
|
13
10
|
// src/context.ts
|
|
@@ -86,9 +83,109 @@ async function guard(fn) {
|
|
|
86
83
|
}
|
|
87
84
|
}
|
|
88
85
|
|
|
86
|
+
// src/account-tools.ts
|
|
87
|
+
var catalogue = [];
|
|
88
|
+
var bridge = null;
|
|
89
|
+
async function loadAccountTools(env, doFetch = globalThis.fetch) {
|
|
90
|
+
const url = (env.ROXYON_ENGAGE_URL ?? "").replace(/\/+$/, "");
|
|
91
|
+
const key = env.ROXYON_CONNECTOR_KEY ?? "";
|
|
92
|
+
if (!url || !key) return 0;
|
|
93
|
+
bridge = { url, key, fetch: doFetch };
|
|
94
|
+
try {
|
|
95
|
+
const res = await doFetch(`${url}/api/connector/tools`, {
|
|
96
|
+
method: "POST",
|
|
97
|
+
headers: { "x-roxyon-connector-key": key },
|
|
98
|
+
signal: AbortSignal.timeout(15e3)
|
|
99
|
+
});
|
|
100
|
+
const body = await res.json();
|
|
101
|
+
if (res.ok && Array.isArray(body.tools))
|
|
102
|
+
catalogue = body.tools.filter((t) => /^[a-z][a-z0-9_]{1,60}$/.test(t.name));
|
|
103
|
+
} catch {
|
|
104
|
+
}
|
|
105
|
+
return catalogue.length;
|
|
106
|
+
}
|
|
107
|
+
function registerAccountTools(server, taken) {
|
|
108
|
+
for (const t of catalogue) {
|
|
109
|
+
const name = `roxyon_${t.name}`;
|
|
110
|
+
if (taken.has(name)) continue;
|
|
111
|
+
taken.add(name);
|
|
112
|
+
server.registerTool(
|
|
113
|
+
name,
|
|
114
|
+
{
|
|
115
|
+
title: `Roxyon: ${t.name.replace(/_/g, " ")}`,
|
|
116
|
+
description: `${t.description}
|
|
117
|
+
|
|
118
|
+
(Acts on the signed-in user's own Roxyon account with their console permissions. Text that says "the client" means this user.)`,
|
|
119
|
+
inputSchema: shapeOf(t.inputSchema),
|
|
120
|
+
annotations: { readOnlyHint: t.readOnly === true, destructiveHint: t.readOnly !== true }
|
|
121
|
+
},
|
|
122
|
+
async (args) => callTool(t.name, args)
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
async function callTool(tool, args) {
|
|
127
|
+
const token = currentContext().token;
|
|
128
|
+
if (!bridge || !token)
|
|
129
|
+
return errorResult("This tool works only through the Roxyon connector at mcp.roxyon.com.");
|
|
130
|
+
try {
|
|
131
|
+
const res = await bridge.fetch(`${bridge.url}/api/connector/call`, {
|
|
132
|
+
method: "POST",
|
|
133
|
+
headers: {
|
|
134
|
+
"content-type": "application/json",
|
|
135
|
+
"x-roxyon-connector-key": bridge.key,
|
|
136
|
+
authorization: `Bearer ${token}`
|
|
137
|
+
},
|
|
138
|
+
body: JSON.stringify({ tool, args }),
|
|
139
|
+
signal: AbortSignal.timeout(6e5)
|
|
140
|
+
});
|
|
141
|
+
const body = await res.json().catch(() => null);
|
|
142
|
+
if (!res.ok)
|
|
143
|
+
return errorResult(body?.message ?? body?.error ?? `Roxyon answered ${res.status}`);
|
|
144
|
+
if (!body?.ok) return errorResult(body?.error ?? "That action failed.");
|
|
145
|
+
const result = body.result;
|
|
146
|
+
return text(
|
|
147
|
+
typeof result === "string" ? result : JSON.stringify(result, null, 2),
|
|
148
|
+
result && typeof result === "object" && !Array.isArray(result) ? result : { result }
|
|
149
|
+
);
|
|
150
|
+
} catch (err) {
|
|
151
|
+
return errorResult(
|
|
152
|
+
`Could not reach Roxyon: ${err instanceof Error ? err.message : String(err)}`
|
|
153
|
+
);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
function shapeOf(schema) {
|
|
157
|
+
const props = schema?.properties ?? {};
|
|
158
|
+
const required = new Set(schema?.required ?? []);
|
|
159
|
+
const shape = {};
|
|
160
|
+
for (const [k, v] of Object.entries(props)) {
|
|
161
|
+
const t = zodOf(v);
|
|
162
|
+
shape[k] = required.has(k) ? t : t.optional();
|
|
163
|
+
}
|
|
164
|
+
return shape;
|
|
165
|
+
}
|
|
166
|
+
function zodOf(s) {
|
|
167
|
+
const type = Array.isArray(s.type) ? s.type.find((x) => x !== "null") : s.type;
|
|
168
|
+
let out;
|
|
169
|
+
if (Array.isArray(s.enum) && s.enum.length && s.enum.every((x) => typeof x === "string")) {
|
|
170
|
+
out = z.enum(s.enum);
|
|
171
|
+
} else if (type === "string") out = z.string();
|
|
172
|
+
else if (type === "number") out = z.number();
|
|
173
|
+
else if (type === "integer") out = z.number().int();
|
|
174
|
+
else if (type === "boolean") out = z.boolean();
|
|
175
|
+
else if (type === "array") out = z.array(s.items ? zodOf(s.items) : z.unknown());
|
|
176
|
+
else if (type === "object")
|
|
177
|
+
out = s.properties ? z.object(shapeOf(s)).passthrough() : z.record(z.unknown());
|
|
178
|
+
else out = z.unknown();
|
|
179
|
+
return s.description ? out.describe(s.description) : out;
|
|
180
|
+
}
|
|
181
|
+
|
|
89
182
|
// src/baas-tools.ts
|
|
183
|
+
import { readFile } from "fs/promises";
|
|
184
|
+
import { join, resolve } from "path";
|
|
185
|
+
import { BAAS_MAX_BATCH, BAAS_MAX_PAGE, BaasDataApi, parseEnv } from "@roxyon/api-client";
|
|
186
|
+
import { z as z2 } from "zod";
|
|
90
187
|
var MAX_TEXT = 4e4;
|
|
91
|
-
var dirArg =
|
|
188
|
+
var dirArg = z2.string().optional().describe("Project directory whose .env holds RX_SECRET_KEY. Defaults to cwd.");
|
|
92
189
|
function remoteBlock() {
|
|
93
190
|
if (!currentContext().remote) return null;
|
|
94
191
|
return errorResult(
|
|
@@ -126,13 +223,13 @@ function registerBaasTools(server) {
|
|
|
126
223
|
description: `Read records of a class in the project's own app on BaaS V2. \`where\` is an object like {"Status":"active","Age":{"gt":18}}; for \`in\`, pass a comma-joined string. \`fields\` defaults to "*"; built-in meta columns must be named. Up to ${BAAS_MAX_PAGE} rows a page (use skip to page). ${SERVER_SCOPE}`,
|
|
127
224
|
inputSchema: {
|
|
128
225
|
dir: dirArg,
|
|
129
|
-
class:
|
|
130
|
-
where:
|
|
131
|
-
fields:
|
|
132
|
-
order:
|
|
133
|
-
limit:
|
|
134
|
-
skip:
|
|
135
|
-
count:
|
|
226
|
+
class: z2.string(),
|
|
227
|
+
where: z2.record(z2.any()).optional(),
|
|
228
|
+
fields: z2.string().optional(),
|
|
229
|
+
order: z2.string().optional().describe('e.g. "-createdAt"'),
|
|
230
|
+
limit: z2.number().int().min(1).max(BAAS_MAX_PAGE).optional(),
|
|
231
|
+
skip: z2.number().int().min(0).optional(),
|
|
232
|
+
count: z2.boolean().optional().describe("Also return the total number of matches.")
|
|
136
233
|
}
|
|
137
234
|
},
|
|
138
235
|
(args) => guard(async () => {
|
|
@@ -157,7 +254,7 @@ ${JSON.stringify(rows, null, 2)}`), { rows, count });
|
|
|
157
254
|
{
|
|
158
255
|
title: "Roxyon BaaS: read one record",
|
|
159
256
|
description: `Read one record by objectId. ${SERVER_SCOPE}`,
|
|
160
|
-
inputSchema: { dir: dirArg, class:
|
|
257
|
+
inputSchema: { dir: dirArg, class: z2.string(), objectId: z2.string() }
|
|
161
258
|
},
|
|
162
259
|
(args) => guard(async () => {
|
|
163
260
|
const blocked = remoteBlock();
|
|
@@ -174,9 +271,9 @@ ${JSON.stringify(rows, null, 2)}`), { rows, count });
|
|
|
174
271
|
description: `Insert one record into an existing class (V2 has no schema changes with a secret key: add classes and columns in the console). For relations store the other row's objectId in a string column. Needs confirm:true. ${SERVER_SCOPE}`,
|
|
175
272
|
inputSchema: {
|
|
176
273
|
dir: dirArg,
|
|
177
|
-
class:
|
|
178
|
-
data:
|
|
179
|
-
confirm:
|
|
274
|
+
class: z2.string(),
|
|
275
|
+
data: z2.record(z2.any()),
|
|
276
|
+
confirm: z2.boolean().optional()
|
|
180
277
|
}
|
|
181
278
|
},
|
|
182
279
|
(args) => guard(async () => {
|
|
@@ -195,10 +292,10 @@ ${JSON.stringify(rows, null, 2)}`), { rows, count });
|
|
|
195
292
|
description: `Set fields on one record by objectId. Needs confirm:true. ${SERVER_SCOPE}`,
|
|
196
293
|
inputSchema: {
|
|
197
294
|
dir: dirArg,
|
|
198
|
-
class:
|
|
199
|
-
objectId:
|
|
200
|
-
data:
|
|
201
|
-
confirm:
|
|
295
|
+
class: z2.string(),
|
|
296
|
+
objectId: z2.string(),
|
|
297
|
+
data: z2.record(z2.any()),
|
|
298
|
+
confirm: z2.boolean().optional()
|
|
202
299
|
}
|
|
203
300
|
},
|
|
204
301
|
(args) => guard(async () => {
|
|
@@ -219,9 +316,9 @@ ${JSON.stringify(rows, null, 2)}`), { rows, count });
|
|
|
219
316
|
description: `Delete one record by objectId. Needs confirm:true. ${SERVER_SCOPE}`,
|
|
220
317
|
inputSchema: {
|
|
221
318
|
dir: dirArg,
|
|
222
|
-
class:
|
|
223
|
-
objectId:
|
|
224
|
-
confirm:
|
|
319
|
+
class: z2.string(),
|
|
320
|
+
objectId: z2.string(),
|
|
321
|
+
confirm: z2.boolean().optional()
|
|
225
322
|
}
|
|
226
323
|
},
|
|
227
324
|
(args) => guard(async () => {
|
|
@@ -240,14 +337,14 @@ ${JSON.stringify(rows, null, 2)}`), { rows, count });
|
|
|
240
337
|
description: `1 to ${BAAS_MAX_BATCH} record requests in one call, each answered on its own (one failing doesn't stop the others). path is /Class or /Class/<objectId>; for POST/PUT the body is {"data":{...}}; for GET the body holds query options (where, fields, limit\u2026). A batch with any write needs confirm:true. ${SERVER_SCOPE}`,
|
|
241
338
|
inputSchema: {
|
|
242
339
|
dir: dirArg,
|
|
243
|
-
requests:
|
|
244
|
-
|
|
245
|
-
method:
|
|
246
|
-
path:
|
|
247
|
-
body:
|
|
340
|
+
requests: z2.array(
|
|
341
|
+
z2.object({
|
|
342
|
+
method: z2.enum(["GET", "POST", "PUT", "DELETE"]),
|
|
343
|
+
path: z2.string(),
|
|
344
|
+
body: z2.record(z2.any()).optional()
|
|
248
345
|
})
|
|
249
346
|
).min(1).max(BAAS_MAX_BATCH),
|
|
250
|
-
confirm:
|
|
347
|
+
confirm: z2.boolean().optional()
|
|
251
348
|
}
|
|
252
349
|
},
|
|
253
350
|
(args) => guard(async () => {
|
|
@@ -276,7 +373,7 @@ ${JSON.stringify(results, null, 2)}`
|
|
|
276
373
|
// src/commerce-tools.ts
|
|
277
374
|
import { BILLING_CYCLES, Roxyon as Roxyon2 } from "@roxyon/api-client";
|
|
278
375
|
import { endpointsFromEnv as endpointsFromEnv2 } from "@roxyon/deploy-core";
|
|
279
|
-
import { z as
|
|
376
|
+
import { z as z3 } from "zod";
|
|
280
377
|
function publicRoxyon() {
|
|
281
378
|
const { apiUrl, consoleUrl } = endpointsFromEnv2();
|
|
282
379
|
return new Roxyon2({ baseUrl: apiUrl, consoleUrl });
|
|
@@ -296,8 +393,8 @@ function registerCommerceTools(server) {
|
|
|
296
393
|
title: "Roxyon: product catalog",
|
|
297
394
|
description: 'List what Roxyon sells: cloud servers (cloud.vps.launch / scale / power) and web hosting (cloud.hosting.solo / multiple / isolated). Each product has its planId (what roxyon_quote takes), SKU, specs (vCPU, RAM GB, disk GB, IPv4 included) for servers, list prices in USD per billing cycle BEFORE tax and currency conversion, and stock ("out" = cannot be ordered now). No sign-in needed. Use roxyon_quote for the exact amount a buyer would pay. Read-only.',
|
|
298
395
|
inputSchema: {
|
|
299
|
-
category:
|
|
300
|
-
in_stock_only:
|
|
396
|
+
category: z3.string().optional().describe('Filter by category prefix, e.g. "cloud.vps" or "cloud.hosting".'),
|
|
397
|
+
in_stock_only: z3.boolean().optional()
|
|
301
398
|
}
|
|
302
399
|
},
|
|
303
400
|
(args) => guard(async () => {
|
|
@@ -316,10 +413,10 @@ function registerCommerceTools(server) {
|
|
|
316
413
|
title: "Roxyon: price a new subscription",
|
|
317
414
|
description: "The exact amount Roxyon's checkout would charge for a NEW subscription to one plan, computed by the platform itself (same server pricing the console checkout uses, setup fee included): line items, subtotal, tax, total, in the requested currency. Pass the buyer currency and country: without them the quote is in USD with no tax, while checkout uses the account's own currency/country. No sign-in needed. Read-only: creates no order. Domains are not priced by this tool yet.",
|
|
318
415
|
inputSchema: {
|
|
319
|
-
plan:
|
|
320
|
-
cycle:
|
|
321
|
-
currency:
|
|
322
|
-
country:
|
|
416
|
+
plan: z3.string().describe('planId (or SKU, e.g. "SCALE-S") from roxyon_catalog.'),
|
|
417
|
+
cycle: z3.enum(BILLING_CYCLES),
|
|
418
|
+
currency: z3.string().length(3).optional().describe('ISO 4217, e.g. "USD", "SAR".'),
|
|
419
|
+
country: z3.string().length(2).optional().describe('ISO 3166 alpha-2, decides tax, e.g. "SA".')
|
|
323
420
|
}
|
|
324
421
|
},
|
|
325
422
|
(args) => guard(async () => {
|
|
@@ -368,8 +465,8 @@ function registerCommerceTools(server) {
|
|
|
368
465
|
title: "Roxyon: my invoices",
|
|
369
466
|
description: "The signed-in user's own invoices, newest first: number, type, status (pending = unpaid, paid, overdue, void, failed, cancelled), total and currency, due date, and payUrl (the console page where the user pays; null when paid). Read-only: this never pays anything.",
|
|
370
467
|
inputSchema: {
|
|
371
|
-
status:
|
|
372
|
-
limit:
|
|
468
|
+
status: z3.enum(["pending", "paid", "overdue", "void", "failed", "cancelled"]).optional(),
|
|
469
|
+
limit: z3.number().int().min(1).max(100).optional().describe("Default 25.")
|
|
373
470
|
}
|
|
374
471
|
},
|
|
375
472
|
(args) => guard(async () => {
|
|
@@ -390,7 +487,7 @@ function registerCommerceTools(server) {
|
|
|
390
487
|
title: "Roxyon: where to pay an invoice",
|
|
391
488
|
description: 'The URL where the signed-in user pays one of their own open invoices (status pending, overdue or failed), plus its amount and due date. Give the invoice number (e.g. "INV2610055") or id from roxyon_list_invoices. A human opens the link and pays in the console; this tool never charges anything. Paid or void invoices have no link.',
|
|
392
489
|
inputSchema: {
|
|
393
|
-
invoice:
|
|
490
|
+
invoice: z3.string().min(1).max(40).describe("Invoice number or id.")
|
|
394
491
|
}
|
|
395
492
|
},
|
|
396
493
|
(args) => guard(async () => {
|
|
@@ -422,16 +519,16 @@ Pay at ${inv.payUrl} (sign in, open Invoices, choose this invoice).`,
|
|
|
422
519
|
title: "Roxyon: place an order (a person pays)",
|
|
423
520
|
description: 'Order a new web-hosting plan or cloud server for the signed-in user. The platform prices it and creates the subscription and a pending invoice; a PERSON then pays at the returned payUrl (nothing is charged by this tool, and the service is set up only after payment). Always call it first WITHOUT confirm: that dry run returns the exact quote and the token budget and writes nothing. Show it to the user, then call again with confirm:true. Rules: `sku` from roxyon_catalog (out-of-stock plans are refused); servers need `image` (e.g. "ubuntu-24.04"); `username` must be free. Needs a sign-in, or a token created with the "billing:order" scope (`roxyon token create <name> --scopes billing:order --budget <USD> [--skus SCALE-S,\u2026]`): it is refused past that monthly budget (USD, before tax) or outside its SKUs.',
|
|
424
521
|
inputSchema: {
|
|
425
|
-
sku:
|
|
426
|
-
cycle:
|
|
427
|
-
name:
|
|
428
|
-
username:
|
|
429
|
-
image:
|
|
430
|
-
ssh_key:
|
|
431
|
-
php_version:
|
|
432
|
-
currency:
|
|
433
|
-
country:
|
|
434
|
-
confirm:
|
|
522
|
+
sku: z3.string().describe('Plan code from roxyon_catalog, e.g. "SCALE-S".'),
|
|
523
|
+
cycle: z3.enum(BILLING_CYCLES),
|
|
524
|
+
name: z3.string().min(1).max(100).describe("A name for the service."),
|
|
525
|
+
username: z3.string().regex(/^[a-z0-9][a-z0-9.-]{1,62}$/).describe("Account/server name: lowercase letters, digits, dots, dashes; must be free."),
|
|
526
|
+
image: z3.string().optional().describe('Servers only: OS image, e.g. "ubuntu-24.04".'),
|
|
527
|
+
ssh_key: z3.string().optional().describe("Servers only: an SSH public key for root."),
|
|
528
|
+
php_version: z3.string().optional().describe('Hosting only, e.g. "8.3".'),
|
|
529
|
+
currency: z3.string().length(3).optional(),
|
|
530
|
+
country: z3.string().length(2).optional().describe('Decides tax, e.g. "SA".'),
|
|
531
|
+
confirm: z3.boolean().optional()
|
|
435
532
|
}
|
|
436
533
|
},
|
|
437
534
|
(args) => guard(async () => {
|
|
@@ -475,7 +572,7 @@ Pay at ${inv.payUrl} (sign in, open Invoices, choose this invoice).`,
|
|
|
475
572
|
}
|
|
476
573
|
|
|
477
574
|
// src/prompts.ts
|
|
478
|
-
import { z as
|
|
575
|
+
import { z as z4 } from "zod";
|
|
479
576
|
function registerPrompts(server) {
|
|
480
577
|
server.registerPrompt(
|
|
481
578
|
"scaffold-lumen-app",
|
|
@@ -483,8 +580,8 @@ function registerPrompts(server) {
|
|
|
483
580
|
title: "Scaffold a LumenJS + Roxyon app",
|
|
484
581
|
description: "Guidance for creating a new LumenJS SPA wired to the Roxyon BaaS and ready to deploy.",
|
|
485
582
|
argsSchema: {
|
|
486
|
-
idea:
|
|
487
|
-
dir:
|
|
583
|
+
idea: z4.string().describe("What the app should do."),
|
|
584
|
+
dir: z4.string().optional().describe("Where to create it.")
|
|
488
585
|
}
|
|
489
586
|
},
|
|
490
587
|
({ idea, dir }) => ({
|
|
@@ -511,8 +608,8 @@ Steps:
|
|
|
511
608
|
title: "Build a site and host it on Roxyon",
|
|
512
609
|
description: "Generate a static site (or no-build LumenJS SPA) in the conversation and publish it to a Roxyon host \u2014 for the hosted connector, no local files.",
|
|
513
610
|
argsSchema: {
|
|
514
|
-
brief:
|
|
515
|
-
host:
|
|
611
|
+
brief: z4.string().describe("What the site should be."),
|
|
612
|
+
host: z4.string().describe("Where to host it, e.g. promo.mycompany.com")
|
|
516
613
|
}
|
|
517
614
|
},
|
|
518
615
|
({ brief, host }) => ({
|
|
@@ -541,7 +638,7 @@ Brief: ${brief}
|
|
|
541
638
|
title: "Install WordPress on Roxyon",
|
|
542
639
|
description: "Provision a host + database and install WordPress core \u2014 local/CLI MCP only.",
|
|
543
640
|
argsSchema: {
|
|
544
|
-
host:
|
|
641
|
+
host: z4.string().describe("Where to install it, e.g. blog.mycompany.com")
|
|
545
642
|
}
|
|
546
643
|
},
|
|
547
644
|
({ host }) => ({
|
|
@@ -566,7 +663,7 @@ Brief: ${brief}
|
|
|
566
663
|
{
|
|
567
664
|
title: "Deploy this project to Roxyon",
|
|
568
665
|
description: "Walk through deploying the current project to Roxyon infrastructure.",
|
|
569
|
-
argsSchema: { dir:
|
|
666
|
+
argsSchema: { dir: z4.string().describe("Absolute path to the project.") }
|
|
570
667
|
},
|
|
571
668
|
({ dir }) => ({
|
|
572
669
|
messages: [
|
|
@@ -660,10 +757,15 @@ import {
|
|
|
660
757
|
packFiles,
|
|
661
758
|
saveProjectConfig
|
|
662
759
|
} from "@roxyon/deploy-core";
|
|
663
|
-
import { z as
|
|
760
|
+
import { z as z5 } from "zod";
|
|
664
761
|
var MAX_FILES = 60;
|
|
665
762
|
var MAX_FILE_BYTES = 1024 * 1024;
|
|
666
763
|
var MAX_TOTAL_BYTES = 2 * 1024 * 1024;
|
|
764
|
+
function engineLabel(engine) {
|
|
765
|
+
if (engine === "postgres") return "PostgreSQL database";
|
|
766
|
+
if (engine === "redis") return "Redis";
|
|
767
|
+
return "MySQL database";
|
|
768
|
+
}
|
|
667
769
|
function remoteFilesystemBlock(tool) {
|
|
668
770
|
if (!currentContext().remote) return null;
|
|
669
771
|
return errorResult(
|
|
@@ -758,11 +860,11 @@ function registerTools(server) {
|
|
|
758
860
|
title: "Roxyon: initialise a project",
|
|
759
861
|
description: "Detect a project's type and write roxyon.json. Run this once per project before roxyon_deploy. Does not create anything on the platform.",
|
|
760
862
|
inputSchema: {
|
|
761
|
-
dir:
|
|
762
|
-
runtime:
|
|
763
|
-
host:
|
|
764
|
-
folder:
|
|
765
|
-
overwrite:
|
|
863
|
+
dir: z5.string().describe("Absolute path to the project directory."),
|
|
864
|
+
runtime: z5.enum(["lumen", "node", "python", "php"]).optional(),
|
|
865
|
+
host: z5.string().optional().describe("Deploy host (a domain on the subscription)."),
|
|
866
|
+
folder: z5.string().optional().describe('Sub-path under the host; "" = site root.'),
|
|
867
|
+
overwrite: z5.boolean().optional().describe("Replace an existing roxyon.json.")
|
|
766
868
|
}
|
|
767
869
|
},
|
|
768
870
|
(args) => guard(async () => {
|
|
@@ -813,10 +915,10 @@ ${JSON.stringify(config, null, 2)}`,
|
|
|
813
915
|
title: "Roxyon: deploy a project",
|
|
814
916
|
description: 'Build (if configured) and deploy the project in "dir" to Roxyon. Without confirm:true this returns a dry-run plan (what would build, where it would go, how many files). With confirm:true it runs the build, uploads, and \u2014 for app runtimes \u2014 waits for it to go live. Requires roxyon.json (run roxyon_init first).',
|
|
815
917
|
inputSchema: {
|
|
816
|
-
dir:
|
|
817
|
-
confirm:
|
|
818
|
-
build:
|
|
819
|
-
follow:
|
|
918
|
+
dir: z5.string().describe("Absolute path to the project directory (must contain roxyon.json)."),
|
|
919
|
+
confirm: z5.boolean().optional().describe("Actually deploy. Omit for a dry-run plan."),
|
|
920
|
+
build: z5.boolean().optional().describe("Run the configured build command (default true)."),
|
|
921
|
+
follow: z5.boolean().optional().describe("For app runtimes, wait until running/failed (default true).")
|
|
820
922
|
}
|
|
821
923
|
},
|
|
822
924
|
(args) => guard(async () => {
|
|
@@ -897,8 +999,8 @@ ${JSON.stringify(config, null, 2)}`,
|
|
|
897
999
|
title: "Roxyon: application status",
|
|
898
1000
|
description: "Current status, revisions and last error for an application (by id or project dir).",
|
|
899
1001
|
inputSchema: {
|
|
900
|
-
application:
|
|
901
|
-
dir:
|
|
1002
|
+
application: z5.string().optional(),
|
|
1003
|
+
dir: z5.string().optional().describe("A project directory whose roxyon.json links an application.")
|
|
902
1004
|
}
|
|
903
1005
|
},
|
|
904
1006
|
(args) => guard(async () => {
|
|
@@ -932,9 +1034,9 @@ ${app.lastError}` : ""
|
|
|
932
1034
|
title: "Roxyon: application logs",
|
|
933
1035
|
description: "Recent journal lines for an application (by id or project dir).",
|
|
934
1036
|
inputSchema: {
|
|
935
|
-
application:
|
|
936
|
-
dir:
|
|
937
|
-
lines:
|
|
1037
|
+
application: z5.string().optional(),
|
|
1038
|
+
dir: z5.string().optional(),
|
|
1039
|
+
lines: z5.number().int().min(1).max(1e3).optional().describe("How many lines (default 100).")
|
|
938
1040
|
}
|
|
939
1041
|
},
|
|
940
1042
|
(args) => guard(async () => {
|
|
@@ -950,9 +1052,9 @@ ${app.lastError}` : ""
|
|
|
950
1052
|
title: "Roxyon: restart an application",
|
|
951
1053
|
description: "Bounce the application process (no rebuild). Needs confirm:true.",
|
|
952
1054
|
inputSchema: {
|
|
953
|
-
application:
|
|
954
|
-
dir:
|
|
955
|
-
confirm:
|
|
1055
|
+
application: z5.string().optional(),
|
|
1056
|
+
dir: z5.string().optional(),
|
|
1057
|
+
confirm: z5.boolean().optional()
|
|
956
1058
|
}
|
|
957
1059
|
},
|
|
958
1060
|
(args) => guard(async () => {
|
|
@@ -969,7 +1071,7 @@ ${app.lastError}` : ""
|
|
|
969
1071
|
{
|
|
970
1072
|
title: "Roxyon: read environment variables",
|
|
971
1073
|
description: "An application's environment variables as KEY=value lines.",
|
|
972
|
-
inputSchema: { application:
|
|
1074
|
+
inputSchema: { application: z5.string().optional(), dir: z5.string().optional() }
|
|
973
1075
|
},
|
|
974
1076
|
(args) => guard(async () => {
|
|
975
1077
|
const session = await getSession();
|
|
@@ -984,11 +1086,11 @@ ${app.lastError}` : ""
|
|
|
984
1086
|
title: "Roxyon: set environment variables",
|
|
985
1087
|
description: "Merge variables into an application's environment and bump its revision. Run roxyon_deploy afterwards to apply. Needs confirm:true. PORT and HOST are platform-managed and ignored.",
|
|
986
1088
|
inputSchema: {
|
|
987
|
-
application:
|
|
988
|
-
dir:
|
|
989
|
-
vars:
|
|
990
|
-
remove:
|
|
991
|
-
confirm:
|
|
1089
|
+
application: z5.string().optional(),
|
|
1090
|
+
dir: z5.string().optional(),
|
|
1091
|
+
vars: z5.record(z5.string()).describe('{ KEY: "value", ... } to set.'),
|
|
1092
|
+
remove: z5.array(z5.string()).optional().describe("Keys to delete."),
|
|
1093
|
+
confirm: z5.boolean().optional()
|
|
992
1094
|
}
|
|
993
1095
|
},
|
|
994
1096
|
(args) => guard(async () => {
|
|
@@ -1023,11 +1125,11 @@ ${app.lastError}` : ""
|
|
|
1023
1125
|
title: "Roxyon: connect a git remote",
|
|
1024
1126
|
description: "Connect a git repository to an application for push-to-deploy. Returns the deploy key and webhook URL to add to the repo. Needs confirm:true.",
|
|
1025
1127
|
inputSchema: {
|
|
1026
|
-
application:
|
|
1027
|
-
dir:
|
|
1028
|
-
repoUrl:
|
|
1029
|
-
branch:
|
|
1030
|
-
confirm:
|
|
1128
|
+
application: z5.string().optional(),
|
|
1129
|
+
dir: z5.string().optional(),
|
|
1130
|
+
repoUrl: z5.string().describe("SSH git URL, e.g. git@github.com:you/app.git"),
|
|
1131
|
+
branch: z5.string().optional().describe("Branch to deploy (default main)."),
|
|
1132
|
+
confirm: z5.boolean().optional()
|
|
1031
1133
|
}
|
|
1032
1134
|
},
|
|
1033
1135
|
(args) => guard(async () => {
|
|
@@ -1069,13 +1171,13 @@ ${res.hint}` : ""
|
|
|
1069
1171
|
title: "Roxyon: add a subdomain / host",
|
|
1070
1172
|
description: "Provision a host \u2014 DNS + web server + automatic HTTPS. The host must be a subdomain of a domain the account already hosts (or a *.roxyon.com subdomain). Returns while it is still coming up; TLS follows a minute or two later. Needs confirm:true.",
|
|
1071
1173
|
inputSchema: {
|
|
1072
|
-
host:
|
|
1073
|
-
subscription:
|
|
1074
|
-
spa:
|
|
1075
|
-
phpVersion:
|
|
1174
|
+
host: z5.string().describe("The full hostname, e.g. promo.mycompany.com"),
|
|
1175
|
+
subscription: z5.string().optional().describe("Which subscription to attach it to (omit if the account has one)."),
|
|
1176
|
+
spa: z5.boolean().optional().describe("Single-page app: unmatched paths serve /index.html."),
|
|
1177
|
+
phpVersion: z5.string().optional().describe(
|
|
1076
1178
|
'PHP version for the vhost, e.g. "8.3" (needed to run PHP apps like WordPress).'
|
|
1077
1179
|
),
|
|
1078
|
-
confirm:
|
|
1180
|
+
confirm: z5.boolean().optional()
|
|
1079
1181
|
}
|
|
1080
1182
|
},
|
|
1081
1183
|
(args) => guard(async () => {
|
|
@@ -1114,18 +1216,18 @@ ${res.hint}` : ""
|
|
|
1114
1216
|
`${(MAX_TOTAL_BYTES / 1024 / 1024).toFixed(1)} MB total (base64-encode binaries).`
|
|
1115
1217
|
].join(" "),
|
|
1116
1218
|
inputSchema: {
|
|
1117
|
-
host:
|
|
1118
|
-
folder:
|
|
1119
|
-
files:
|
|
1120
|
-
|
|
1121
|
-
path:
|
|
1122
|
-
content:
|
|
1123
|
-
encoding:
|
|
1219
|
+
host: z5.string().describe("Target host, e.g. promo.mycompany.com"),
|
|
1220
|
+
folder: z5.string().optional().describe('Sub-path under the host; "" = site root.'),
|
|
1221
|
+
files: z5.array(
|
|
1222
|
+
z5.object({
|
|
1223
|
+
path: z5.string().describe("Relative path, e.g. index.html, about/index.html"),
|
|
1224
|
+
content: z5.string(),
|
|
1225
|
+
encoding: z5.enum(["utf8", "base64"]).optional()
|
|
1124
1226
|
})
|
|
1125
1227
|
).min(1),
|
|
1126
|
-
clean:
|
|
1127
|
-
spa:
|
|
1128
|
-
confirm:
|
|
1228
|
+
clean: z5.boolean().optional().describe("Replace the whole docroot (keeps .well-known)."),
|
|
1229
|
+
spa: z5.boolean().optional().describe("Also flip the host to SPA routing."),
|
|
1230
|
+
confirm: z5.boolean().optional()
|
|
1129
1231
|
}
|
|
1130
1232
|
},
|
|
1131
1233
|
(args) => guard(async () => {
|
|
@@ -1182,9 +1284,9 @@ ${res.hint}` : ""
|
|
|
1182
1284
|
title: "Roxyon: list a site\u2019s files",
|
|
1183
1285
|
description: "The files currently deployed to a host's document root.",
|
|
1184
1286
|
inputSchema: {
|
|
1185
|
-
host:
|
|
1186
|
-
folder:
|
|
1187
|
-
path:
|
|
1287
|
+
host: z5.string(),
|
|
1288
|
+
folder: z5.string().optional(),
|
|
1289
|
+
path: z5.string().optional().describe("List under this sub-path only.")
|
|
1188
1290
|
}
|
|
1189
1291
|
},
|
|
1190
1292
|
(args) => guard(async () => {
|
|
@@ -1206,9 +1308,9 @@ ${res.hint}` : ""
|
|
|
1206
1308
|
title: "Roxyon: read a site file",
|
|
1207
1309
|
description: "Read one file from a host's document root (to review or edit it).",
|
|
1208
1310
|
inputSchema: {
|
|
1209
|
-
host:
|
|
1210
|
-
folder:
|
|
1211
|
-
path:
|
|
1311
|
+
host: z5.string(),
|
|
1312
|
+
folder: z5.string().optional(),
|
|
1313
|
+
path: z5.string().describe("Relative path, e.g. index.html")
|
|
1212
1314
|
}
|
|
1213
1315
|
},
|
|
1214
1316
|
(args) => guard(async () => {
|
|
@@ -1228,22 +1330,25 @@ ${f.content.slice(0, 4096)}`,
|
|
|
1228
1330
|
server.registerTool(
|
|
1229
1331
|
"roxyon_database_create",
|
|
1230
1332
|
{
|
|
1231
|
-
title: "Roxyon: create a database",
|
|
1232
|
-
description: `Provision a
|
|
1333
|
+
title: "Roxyon: create a database (MySQL, PostgreSQL or Redis)",
|
|
1334
|
+
description: `Provision a data store on a Roxyon hosting account. engine "mysql" (MySQL/MariaDB, default; localhost:3306), "postgres" (PostgreSQL 15 inside the account, localhost:5432; the first one installs it in a few minutes) or "redis" (Redis inside the account, 127.0.0.1:6379 with a password, 128 MB, saved to disk; one per account; give apps REDIS_URL=redis://:<password>@127.0.0.1:6379). Every hosting account has all three: never tell the user to get a VPS or an outside service for PostgreSQL or Redis. The name is prefixed with the account's own username \u2014 pass just the suffix, e.g. "wordpress" (for Redis any short name, e.g. "cache"). The password is shown once in the result; there is no way to retrieve it later. Needs confirm:true.`,
|
|
1233
1335
|
inputSchema: {
|
|
1234
|
-
name:
|
|
1235
|
-
subscription:
|
|
1236
|
-
password:
|
|
1237
|
-
engine:
|
|
1238
|
-
confirm:
|
|
1336
|
+
name: z5.string().describe("DB name suffix: lowercase letters, digits, underscore."),
|
|
1337
|
+
subscription: z5.string().optional(),
|
|
1338
|
+
password: z5.string().optional().describe("Omit to have Roxyon generate one."),
|
|
1339
|
+
engine: z5.enum(["mysql", "postgres", "redis"]).optional().describe("mysql (default), postgres or redis."),
|
|
1340
|
+
confirm: z5.boolean().optional()
|
|
1239
1341
|
}
|
|
1240
1342
|
},
|
|
1241
1343
|
(args) => guard(async () => {
|
|
1242
1344
|
const session = await getSession();
|
|
1243
1345
|
if (!args.confirm) {
|
|
1244
|
-
return text(
|
|
1245
|
-
|
|
1246
|
-
|
|
1346
|
+
return text(
|
|
1347
|
+
`Would create ${engineLabel(args.engine)} "${args.name}". Pass confirm:true.`,
|
|
1348
|
+
{
|
|
1349
|
+
dryRun: true
|
|
1350
|
+
}
|
|
1351
|
+
);
|
|
1247
1352
|
}
|
|
1248
1353
|
const r = await session.roxyon.databases.create({
|
|
1249
1354
|
name: args.name,
|
|
@@ -1251,9 +1356,8 @@ ${f.content.slice(0, 4096)}`,
|
|
|
1251
1356
|
password: args.password,
|
|
1252
1357
|
...args.engine ? { engine: args.engine } : {}
|
|
1253
1358
|
});
|
|
1254
|
-
const pg = r.engine === "postgres";
|
|
1255
1359
|
const body = [
|
|
1256
|
-
`${r.status === "active" ? "\u2713" : "\xBB"} ${
|
|
1360
|
+
`${r.status === "active" ? "\u2713" : "\xBB"} ${engineLabel(r.engine)} ${r.name}${r.username ? ` (user ${r.username})` : ""} \u2014 ${r.status}.`,
|
|
1257
1361
|
r.password ? `
|
|
1258
1362
|
Password (save this now \u2014 shown once):
|
|
1259
1363
|
${r.password}` : "",
|
|
@@ -1277,14 +1381,16 @@ ${r.password}` : "",
|
|
|
1277
1381
|
{
|
|
1278
1382
|
title: "Roxyon: list databases",
|
|
1279
1383
|
description: "Databases on the active subscription.",
|
|
1280
|
-
inputSchema: { subscription:
|
|
1384
|
+
inputSchema: { subscription: z5.string().optional() }
|
|
1281
1385
|
},
|
|
1282
1386
|
(args) => guard(async () => {
|
|
1283
1387
|
const session = await getSession();
|
|
1284
1388
|
const subscription = args.subscription ?? session.preferredSubscription;
|
|
1285
1389
|
if (!subscription) return errorResult('No subscription \u2014 pass "subscription".');
|
|
1286
1390
|
const rows = await session.roxyon.databases.list(subscription);
|
|
1287
|
-
const body = rows.length ? rows.map(
|
|
1391
|
+
const body = rows.length ? rows.map(
|
|
1392
|
+
(d) => `- ${d.Name} [${d.objectId}] \u2014 ${engineLabel(d.Engine)}, ${d.Status}${d.Engine === "redis" ? " (127.0.0.1:6379)" : ` (user ${d.Username})`}`
|
|
1393
|
+
).join("\n") : "(no databases)";
|
|
1288
1394
|
return text(body, { databases: rows });
|
|
1289
1395
|
})
|
|
1290
1396
|
);
|
|
@@ -1293,7 +1399,7 @@ ${r.password}` : "",
|
|
|
1293
1399
|
{
|
|
1294
1400
|
title: "Roxyon: delete a database",
|
|
1295
1401
|
description: "Delete a database by its objectId (from roxyon_database_create/roxyon_list_databases). Needs confirm:true.",
|
|
1296
|
-
inputSchema: { database:
|
|
1402
|
+
inputSchema: { database: z5.string(), confirm: z5.boolean().optional() }
|
|
1297
1403
|
},
|
|
1298
1404
|
(args) => guard(async () => {
|
|
1299
1405
|
const session = await getSession();
|
|
@@ -1312,13 +1418,13 @@ ${r.password}` : "",
|
|
|
1312
1418
|
title: "Roxyon: create a mailbox",
|
|
1313
1419
|
description: "Provision a mailbox on a host already on the account. The password is shown once in the result; there is no way to retrieve it later. Needs confirm:true.",
|
|
1314
1420
|
inputSchema: {
|
|
1315
|
-
localPart:
|
|
1316
|
-
domain:
|
|
1317
|
-
password:
|
|
1318
|
-
quota:
|
|
1319
|
-
forwardTo:
|
|
1320
|
-
saveCopy:
|
|
1321
|
-
confirm:
|
|
1421
|
+
localPart: z5.string().describe('The part before @, e.g. "info".'),
|
|
1422
|
+
domain: z5.string().describe("A hostname already on the account."),
|
|
1423
|
+
password: z5.string().optional().describe("Omit to have Roxyon generate one."),
|
|
1424
|
+
quota: z5.number().int().positive().optional().describe("Quota in MB (default 1024)."),
|
|
1425
|
+
forwardTo: z5.string().optional(),
|
|
1426
|
+
saveCopy: z5.boolean().optional(),
|
|
1427
|
+
confirm: z5.boolean().optional()
|
|
1322
1428
|
}
|
|
1323
1429
|
},
|
|
1324
1430
|
(args) => guard(async () => {
|
|
@@ -1349,7 +1455,7 @@ ${r.password}` : ""
|
|
|
1349
1455
|
{
|
|
1350
1456
|
title: "Roxyon: list mailboxes",
|
|
1351
1457
|
description: "Mailboxes on the active subscription.",
|
|
1352
|
-
inputSchema: { subscription:
|
|
1458
|
+
inputSchema: { subscription: z5.string().optional() }
|
|
1353
1459
|
},
|
|
1354
1460
|
(args) => guard(async () => {
|
|
1355
1461
|
const session = await getSession();
|
|
@@ -1365,7 +1471,7 @@ ${r.password}` : ""
|
|
|
1365
1471
|
{
|
|
1366
1472
|
title: "Roxyon: delete a mailbox",
|
|
1367
1473
|
description: "Delete a mailbox by its objectId (from roxyon_email_create/roxyon_list_emails). Needs confirm:true.",
|
|
1368
|
-
inputSchema: { email:
|
|
1474
|
+
inputSchema: { email: z5.string(), confirm: z5.boolean().optional() }
|
|
1369
1475
|
},
|
|
1370
1476
|
(args) => guard(async () => {
|
|
1371
1477
|
const session = await getSession();
|
|
@@ -1382,9 +1488,9 @@ ${r.password}` : ""
|
|
|
1382
1488
|
title: "Roxyon: reset the SSH/SFTP password",
|
|
1383
1489
|
description: "Reset a subscription's shell password \u2014 used for SSH, SFTP, and the file manager. Changing it signs out anything using the old one. The password is shown once in the result; the platform never stores or re-shows it. Needs confirm:true.",
|
|
1384
1490
|
inputSchema: {
|
|
1385
|
-
subscription:
|
|
1386
|
-
password:
|
|
1387
|
-
confirm:
|
|
1491
|
+
subscription: z5.string().optional(),
|
|
1492
|
+
password: z5.string().optional().describe("Omit to have Roxyon generate one (12+ chars, mixed case, number, symbol)."),
|
|
1493
|
+
confirm: z5.boolean().optional()
|
|
1388
1494
|
}
|
|
1389
1495
|
},
|
|
1390
1496
|
(args) => guard(async () => {
|
|
@@ -1416,14 +1522,14 @@ ${r.password}`,
|
|
|
1416
1522
|
"this from the CLI-backed MCP, not the hosted mcp.roxyon.com connector. Needs confirm:true."
|
|
1417
1523
|
].join(" "),
|
|
1418
1524
|
inputSchema: {
|
|
1419
|
-
host:
|
|
1420
|
-
folder:
|
|
1421
|
-
subscription:
|
|
1422
|
-
dbName:
|
|
1525
|
+
host: z5.string().describe("The site host, e.g. blog.mycompany.com \u2014 created if it does not exist."),
|
|
1526
|
+
folder: z5.string().optional().describe('Sub-path under the host; "" = site root (recommended).'),
|
|
1527
|
+
subscription: z5.string().optional(),
|
|
1528
|
+
dbName: z5.string().optional().describe(
|
|
1423
1529
|
"Database name suffix (default derived from the host). See roxyon_database_create."
|
|
1424
1530
|
),
|
|
1425
|
-
phpVersion:
|
|
1426
|
-
confirm:
|
|
1531
|
+
phpVersion: z5.string().optional().describe('Default "8.3".'),
|
|
1532
|
+
confirm: z5.boolean().optional()
|
|
1427
1533
|
}
|
|
1428
1534
|
},
|
|
1429
1535
|
(args) => guard(async () => {
|
|
@@ -1489,7 +1595,7 @@ function createServer() {
|
|
|
1489
1595
|
const server = new McpServer(
|
|
1490
1596
|
{ name: SERVER_NAME, version: SERVER_VERSION },
|
|
1491
1597
|
{
|
|
1492
|
-
instructions:
|
|
1598
|
+
instructions: `Roxyon lets you build LumenJS SPAs, use the Roxyon BaaS, and deploy apps and web projects to Roxyon infrastructure. Read the roxyon://docs/* resources before writing LumenJS or calling the BaaS. To deploy: roxyon_init once per project, then roxyon_deploy (dry run, then confirm:true). Side-effecting tools require confirm:true. Every Roxyon hosting account includes MySQL, PostgreSQL and Redis (roxyon_database_create with engine "mysql" | "postgres" | "redis"), mailboxes, domains with free SSL, Node/PHP apps, env vars, logs and SSH: never send the user to a VPS or an outside service for any of these. If something the user asks for has no tool here, say exactly which step is missing and that it can be done in the Roxyon Console or by Roxyon AI there (console.roxyon.com/agent); never invent a workaround or claim it is impossible on Roxyon. Besides the deploy tools, the remote connector offers the same account tools Roxyon AI uses in the console, acting as the signed-in user with their permissions: site files (read, write, move, delete), site_tools (cron jobs, PHP settings, permissions, zip/unzip, search), run_in_site (a shell command in their hosting), DNS records, SSL, mailboxes, forwarding and auto-replies, databases (export, restore, SQL), nightly backups and restores, cloud servers (start/stop/restart, root password, firewall), plans, invoices and payments, Roxyon Base, newsletters, Engage and social posts. Actions that change something need the user's explicit yes first (confirmed: true).`
|
|
1493
1599
|
}
|
|
1494
1600
|
);
|
|
1495
1601
|
registerResources(server);
|
|
@@ -1497,13 +1603,20 @@ function createServer() {
|
|
|
1497
1603
|
registerTools(server);
|
|
1498
1604
|
registerBaasTools(server);
|
|
1499
1605
|
registerCommerceTools(server);
|
|
1606
|
+
const taken = new Set(
|
|
1607
|
+
Object.keys(
|
|
1608
|
+
server._registeredTools
|
|
1609
|
+
)
|
|
1610
|
+
);
|
|
1611
|
+
registerAccountTools(server, taken);
|
|
1500
1612
|
return server;
|
|
1501
1613
|
}
|
|
1502
1614
|
|
|
1503
1615
|
export {
|
|
1504
1616
|
runWithContext,
|
|
1617
|
+
loadAccountTools,
|
|
1505
1618
|
SERVER_NAME,
|
|
1506
1619
|
SERVER_VERSION,
|
|
1507
1620
|
createServer
|
|
1508
1621
|
};
|
|
1509
|
-
//# sourceMappingURL=chunk-
|
|
1622
|
+
//# sourceMappingURL=chunk-BNRJ2C63.js.map
|