@roxyon/mcp 0.9.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.
|
@@ -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,7 +757,7 @@ 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;
|
|
@@ -763,11 +860,11 @@ function registerTools(server) {
|
|
|
763
860
|
title: "Roxyon: initialise a project",
|
|
764
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.",
|
|
765
862
|
inputSchema: {
|
|
766
|
-
dir:
|
|
767
|
-
runtime:
|
|
768
|
-
host:
|
|
769
|
-
folder:
|
|
770
|
-
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.")
|
|
771
868
|
}
|
|
772
869
|
},
|
|
773
870
|
(args) => guard(async () => {
|
|
@@ -818,10 +915,10 @@ ${JSON.stringify(config, null, 2)}`,
|
|
|
818
915
|
title: "Roxyon: deploy a project",
|
|
819
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).',
|
|
820
917
|
inputSchema: {
|
|
821
|
-
dir:
|
|
822
|
-
confirm:
|
|
823
|
-
build:
|
|
824
|
-
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).")
|
|
825
922
|
}
|
|
826
923
|
},
|
|
827
924
|
(args) => guard(async () => {
|
|
@@ -902,8 +999,8 @@ ${JSON.stringify(config, null, 2)}`,
|
|
|
902
999
|
title: "Roxyon: application status",
|
|
903
1000
|
description: "Current status, revisions and last error for an application (by id or project dir).",
|
|
904
1001
|
inputSchema: {
|
|
905
|
-
application:
|
|
906
|
-
dir:
|
|
1002
|
+
application: z5.string().optional(),
|
|
1003
|
+
dir: z5.string().optional().describe("A project directory whose roxyon.json links an application.")
|
|
907
1004
|
}
|
|
908
1005
|
},
|
|
909
1006
|
(args) => guard(async () => {
|
|
@@ -937,9 +1034,9 @@ ${app.lastError}` : ""
|
|
|
937
1034
|
title: "Roxyon: application logs",
|
|
938
1035
|
description: "Recent journal lines for an application (by id or project dir).",
|
|
939
1036
|
inputSchema: {
|
|
940
|
-
application:
|
|
941
|
-
dir:
|
|
942
|
-
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).")
|
|
943
1040
|
}
|
|
944
1041
|
},
|
|
945
1042
|
(args) => guard(async () => {
|
|
@@ -955,9 +1052,9 @@ ${app.lastError}` : ""
|
|
|
955
1052
|
title: "Roxyon: restart an application",
|
|
956
1053
|
description: "Bounce the application process (no rebuild). Needs confirm:true.",
|
|
957
1054
|
inputSchema: {
|
|
958
|
-
application:
|
|
959
|
-
dir:
|
|
960
|
-
confirm:
|
|
1055
|
+
application: z5.string().optional(),
|
|
1056
|
+
dir: z5.string().optional(),
|
|
1057
|
+
confirm: z5.boolean().optional()
|
|
961
1058
|
}
|
|
962
1059
|
},
|
|
963
1060
|
(args) => guard(async () => {
|
|
@@ -974,7 +1071,7 @@ ${app.lastError}` : ""
|
|
|
974
1071
|
{
|
|
975
1072
|
title: "Roxyon: read environment variables",
|
|
976
1073
|
description: "An application's environment variables as KEY=value lines.",
|
|
977
|
-
inputSchema: { application:
|
|
1074
|
+
inputSchema: { application: z5.string().optional(), dir: z5.string().optional() }
|
|
978
1075
|
},
|
|
979
1076
|
(args) => guard(async () => {
|
|
980
1077
|
const session = await getSession();
|
|
@@ -989,11 +1086,11 @@ ${app.lastError}` : ""
|
|
|
989
1086
|
title: "Roxyon: set environment variables",
|
|
990
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.",
|
|
991
1088
|
inputSchema: {
|
|
992
|
-
application:
|
|
993
|
-
dir:
|
|
994
|
-
vars:
|
|
995
|
-
remove:
|
|
996
|
-
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()
|
|
997
1094
|
}
|
|
998
1095
|
},
|
|
999
1096
|
(args) => guard(async () => {
|
|
@@ -1028,11 +1125,11 @@ ${app.lastError}` : ""
|
|
|
1028
1125
|
title: "Roxyon: connect a git remote",
|
|
1029
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.",
|
|
1030
1127
|
inputSchema: {
|
|
1031
|
-
application:
|
|
1032
|
-
dir:
|
|
1033
|
-
repoUrl:
|
|
1034
|
-
branch:
|
|
1035
|
-
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()
|
|
1036
1133
|
}
|
|
1037
1134
|
},
|
|
1038
1135
|
(args) => guard(async () => {
|
|
@@ -1074,13 +1171,13 @@ ${res.hint}` : ""
|
|
|
1074
1171
|
title: "Roxyon: add a subdomain / host",
|
|
1075
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.",
|
|
1076
1173
|
inputSchema: {
|
|
1077
|
-
host:
|
|
1078
|
-
subscription:
|
|
1079
|
-
spa:
|
|
1080
|
-
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(
|
|
1081
1178
|
'PHP version for the vhost, e.g. "8.3" (needed to run PHP apps like WordPress).'
|
|
1082
1179
|
),
|
|
1083
|
-
confirm:
|
|
1180
|
+
confirm: z5.boolean().optional()
|
|
1084
1181
|
}
|
|
1085
1182
|
},
|
|
1086
1183
|
(args) => guard(async () => {
|
|
@@ -1119,18 +1216,18 @@ ${res.hint}` : ""
|
|
|
1119
1216
|
`${(MAX_TOTAL_BYTES / 1024 / 1024).toFixed(1)} MB total (base64-encode binaries).`
|
|
1120
1217
|
].join(" "),
|
|
1121
1218
|
inputSchema: {
|
|
1122
|
-
host:
|
|
1123
|
-
folder:
|
|
1124
|
-
files:
|
|
1125
|
-
|
|
1126
|
-
path:
|
|
1127
|
-
content:
|
|
1128
|
-
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()
|
|
1129
1226
|
})
|
|
1130
1227
|
).min(1),
|
|
1131
|
-
clean:
|
|
1132
|
-
spa:
|
|
1133
|
-
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()
|
|
1134
1231
|
}
|
|
1135
1232
|
},
|
|
1136
1233
|
(args) => guard(async () => {
|
|
@@ -1187,9 +1284,9 @@ ${res.hint}` : ""
|
|
|
1187
1284
|
title: "Roxyon: list a site\u2019s files",
|
|
1188
1285
|
description: "The files currently deployed to a host's document root.",
|
|
1189
1286
|
inputSchema: {
|
|
1190
|
-
host:
|
|
1191
|
-
folder:
|
|
1192
|
-
path:
|
|
1287
|
+
host: z5.string(),
|
|
1288
|
+
folder: z5.string().optional(),
|
|
1289
|
+
path: z5.string().optional().describe("List under this sub-path only.")
|
|
1193
1290
|
}
|
|
1194
1291
|
},
|
|
1195
1292
|
(args) => guard(async () => {
|
|
@@ -1211,9 +1308,9 @@ ${res.hint}` : ""
|
|
|
1211
1308
|
title: "Roxyon: read a site file",
|
|
1212
1309
|
description: "Read one file from a host's document root (to review or edit it).",
|
|
1213
1310
|
inputSchema: {
|
|
1214
|
-
host:
|
|
1215
|
-
folder:
|
|
1216
|
-
path:
|
|
1311
|
+
host: z5.string(),
|
|
1312
|
+
folder: z5.string().optional(),
|
|
1313
|
+
path: z5.string().describe("Relative path, e.g. index.html")
|
|
1217
1314
|
}
|
|
1218
1315
|
},
|
|
1219
1316
|
(args) => guard(async () => {
|
|
@@ -1236,11 +1333,11 @@ ${f.content.slice(0, 4096)}`,
|
|
|
1236
1333
|
title: "Roxyon: create a database (MySQL, PostgreSQL or Redis)",
|
|
1237
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.`,
|
|
1238
1335
|
inputSchema: {
|
|
1239
|
-
name:
|
|
1240
|
-
subscription:
|
|
1241
|
-
password:
|
|
1242
|
-
engine:
|
|
1243
|
-
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()
|
|
1244
1341
|
}
|
|
1245
1342
|
},
|
|
1246
1343
|
(args) => guard(async () => {
|
|
@@ -1284,7 +1381,7 @@ ${r.password}` : "",
|
|
|
1284
1381
|
{
|
|
1285
1382
|
title: "Roxyon: list databases",
|
|
1286
1383
|
description: "Databases on the active subscription.",
|
|
1287
|
-
inputSchema: { subscription:
|
|
1384
|
+
inputSchema: { subscription: z5.string().optional() }
|
|
1288
1385
|
},
|
|
1289
1386
|
(args) => guard(async () => {
|
|
1290
1387
|
const session = await getSession();
|
|
@@ -1302,7 +1399,7 @@ ${r.password}` : "",
|
|
|
1302
1399
|
{
|
|
1303
1400
|
title: "Roxyon: delete a database",
|
|
1304
1401
|
description: "Delete a database by its objectId (from roxyon_database_create/roxyon_list_databases). Needs confirm:true.",
|
|
1305
|
-
inputSchema: { database:
|
|
1402
|
+
inputSchema: { database: z5.string(), confirm: z5.boolean().optional() }
|
|
1306
1403
|
},
|
|
1307
1404
|
(args) => guard(async () => {
|
|
1308
1405
|
const session = await getSession();
|
|
@@ -1321,13 +1418,13 @@ ${r.password}` : "",
|
|
|
1321
1418
|
title: "Roxyon: create a mailbox",
|
|
1322
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.",
|
|
1323
1420
|
inputSchema: {
|
|
1324
|
-
localPart:
|
|
1325
|
-
domain:
|
|
1326
|
-
password:
|
|
1327
|
-
quota:
|
|
1328
|
-
forwardTo:
|
|
1329
|
-
saveCopy:
|
|
1330
|
-
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()
|
|
1331
1428
|
}
|
|
1332
1429
|
},
|
|
1333
1430
|
(args) => guard(async () => {
|
|
@@ -1358,7 +1455,7 @@ ${r.password}` : ""
|
|
|
1358
1455
|
{
|
|
1359
1456
|
title: "Roxyon: list mailboxes",
|
|
1360
1457
|
description: "Mailboxes on the active subscription.",
|
|
1361
|
-
inputSchema: { subscription:
|
|
1458
|
+
inputSchema: { subscription: z5.string().optional() }
|
|
1362
1459
|
},
|
|
1363
1460
|
(args) => guard(async () => {
|
|
1364
1461
|
const session = await getSession();
|
|
@@ -1374,7 +1471,7 @@ ${r.password}` : ""
|
|
|
1374
1471
|
{
|
|
1375
1472
|
title: "Roxyon: delete a mailbox",
|
|
1376
1473
|
description: "Delete a mailbox by its objectId (from roxyon_email_create/roxyon_list_emails). Needs confirm:true.",
|
|
1377
|
-
inputSchema: { email:
|
|
1474
|
+
inputSchema: { email: z5.string(), confirm: z5.boolean().optional() }
|
|
1378
1475
|
},
|
|
1379
1476
|
(args) => guard(async () => {
|
|
1380
1477
|
const session = await getSession();
|
|
@@ -1391,9 +1488,9 @@ ${r.password}` : ""
|
|
|
1391
1488
|
title: "Roxyon: reset the SSH/SFTP password",
|
|
1392
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.",
|
|
1393
1490
|
inputSchema: {
|
|
1394
|
-
subscription:
|
|
1395
|
-
password:
|
|
1396
|
-
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()
|
|
1397
1494
|
}
|
|
1398
1495
|
},
|
|
1399
1496
|
(args) => guard(async () => {
|
|
@@ -1425,14 +1522,14 @@ ${r.password}`,
|
|
|
1425
1522
|
"this from the CLI-backed MCP, not the hosted mcp.roxyon.com connector. Needs confirm:true."
|
|
1426
1523
|
].join(" "),
|
|
1427
1524
|
inputSchema: {
|
|
1428
|
-
host:
|
|
1429
|
-
folder:
|
|
1430
|
-
subscription:
|
|
1431
|
-
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(
|
|
1432
1529
|
"Database name suffix (default derived from the host). See roxyon_database_create."
|
|
1433
1530
|
),
|
|
1434
|
-
phpVersion:
|
|
1435
|
-
confirm:
|
|
1531
|
+
phpVersion: z5.string().optional().describe('Default "8.3".'),
|
|
1532
|
+
confirm: z5.boolean().optional()
|
|
1436
1533
|
}
|
|
1437
1534
|
},
|
|
1438
1535
|
(args) => guard(async () => {
|
|
@@ -1498,7 +1595,7 @@ function createServer() {
|
|
|
1498
1595
|
const server = new McpServer(
|
|
1499
1596
|
{ name: SERVER_NAME, version: SERVER_VERSION },
|
|
1500
1597
|
{
|
|
1501
|
-
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).`
|
|
1502
1599
|
}
|
|
1503
1600
|
);
|
|
1504
1601
|
registerResources(server);
|
|
@@ -1506,13 +1603,20 @@ function createServer() {
|
|
|
1506
1603
|
registerTools(server);
|
|
1507
1604
|
registerBaasTools(server);
|
|
1508
1605
|
registerCommerceTools(server);
|
|
1606
|
+
const taken = new Set(
|
|
1607
|
+
Object.keys(
|
|
1608
|
+
server._registeredTools
|
|
1609
|
+
)
|
|
1610
|
+
);
|
|
1611
|
+
registerAccountTools(server, taken);
|
|
1509
1612
|
return server;
|
|
1510
1613
|
}
|
|
1511
1614
|
|
|
1512
1615
|
export {
|
|
1513
1616
|
runWithContext,
|
|
1617
|
+
loadAccountTools,
|
|
1514
1618
|
SERVER_NAME,
|
|
1515
1619
|
SERVER_VERSION,
|
|
1516
1620
|
createServer
|
|
1517
1621
|
};
|
|
1518
|
-
//# sourceMappingURL=chunk-
|
|
1622
|
+
//# sourceMappingURL=chunk-BNRJ2C63.js.map
|