alora-mcp 0.11.1 → 0.12.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/README.md +2 -1
- package/package.json +2 -2
- package/src/index.js +49 -2
package/README.md
CHANGED
|
@@ -36,13 +36,14 @@ Prefer the classic way? Create an account at [alora.build/register](https://alor
|
|
|
36
36
|
|
|
37
37
|
Either way the token lands in `~/.alora/config.json`, and from then on your agent takes it from here. Full playbook for agents: [alora.build/llms.txt](https://alora.build/llms.txt).
|
|
38
38
|
|
|
39
|
-
## What your agent can do (
|
|
39
|
+
## What your agent can do (51 tools)
|
|
40
40
|
|
|
41
41
|
| | |
|
|
42
42
|
|---|---|
|
|
43
43
|
| **Ship** | `create_project` · `deploy` (Node, Python, static, or any command via Procfile/alora.json) · `list_versions` · `rollback` |
|
|
44
44
|
| **Preview before prod** | `preview` → temporary URL at `myapp--preview.alora.page` with throwaway data · `promote` · `preview_stop` |
|
|
45
45
|
| **Share** | apps are born private — `share` by email with roles, `set_visibility`, `revoke_access` |
|
|
46
|
+
| **Give away a copy** | `project_send_copy` — the other person gets an independent project in *their* account (code only: no data, secrets, members or Claude credential; Alora flags `.env`/`.db`-style files before sending) · `project_clone` for a copy in your own account · `pull` downloads a project's code to edit it |
|
|
46
47
|
| **Run things** | `set_always_on` / `set_worker_mode` for 24/7 processes · `cron_create` for scheduled hits · `exec` · `restart` · `logs` |
|
|
47
48
|
| **Configure** | `secrets_set` (encrypted, injected as env) · `domain_connect` · `egress_ip_allocate` (static egress IP) |
|
|
48
49
|
| **Use AI inside the app** | `ai_enable` — the deployed app runs `claude -p` with the owner's own Claude account |
|
package/package.json
CHANGED
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "alora-mcp",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "MCP server for Alora, the cloud where your agent works — deploy, preview, share and run the apps your agent writes. Claude Code, Codex, Cursor and any MCP client.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
7
|
"alora-mcp": "src/index.js"
|
|
8
8
|
},
|
|
9
9
|
"dependencies": {
|
|
10
|
-
"alora-sdk": "^0.
|
|
10
|
+
"alora-sdk": "^0.12.0",
|
|
11
11
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
12
12
|
"tar": "^7.4.3",
|
|
13
13
|
"zod": "^3.24.0"
|
package/src/index.js
CHANGED
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
5
5
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
6
6
|
import { z } from 'zod';
|
|
7
|
-
import { readFileSync, rmSync, writeFileSync, mkdirSync } from 'node:fs';
|
|
7
|
+
import { readFileSync, rmSync, writeFileSync, mkdirSync, existsSync, readdirSync } from 'node:fs';
|
|
8
8
|
import { homedir, tmpdir } from 'node:os';
|
|
9
9
|
import path from 'node:path';
|
|
10
10
|
import * as tar from 'tar';
|
|
@@ -18,7 +18,7 @@ function client() {
|
|
|
18
18
|
return new AloraClient({ api: cfg.api || 'https://api.alora.build', token: cfg.token });
|
|
19
19
|
}
|
|
20
20
|
|
|
21
|
-
const server = new McpServer({ name: 'alora', version: '0.
|
|
21
|
+
const server = new McpServer({ name: 'alora', version: '0.12.0' });
|
|
22
22
|
|
|
23
23
|
const ok = (data) => ({ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] });
|
|
24
24
|
const asError = (e) => ({
|
|
@@ -236,6 +236,53 @@ server.registerTool('set_visibility', {
|
|
|
236
236
|
inputSchema: { project: z.string(), visibility: z.enum(['private', 'public']) },
|
|
237
237
|
}, wrap(({ project, visibility }) => client().setVisibility(project, visibility)));
|
|
238
238
|
|
|
239
|
+
// --- copias: darle a alguien SU propia copia / clonar / bajar el código ---
|
|
240
|
+
server.registerTool('project_send_copy', {
|
|
241
|
+
description: 'Le da a OTRA persona su propia copia de un proyecto: un proyecto independiente en SU cuenta, con su saldo, sus datos y su propio Claude — el remitente no ve nada de la copia (a diferencia de "share", que solo deja USAR tu app). Viaja SOLO el código de una versión (la activa por default): nunca el volumen de datos, secrets, miembros, crons, always-on ni la credencial de Claude; la respuesta lista lo que quedó afuera (left_out) para avisarle al humano qué va a necesitar configurar el otro. Alora escanea el bundle antes: si trae archivos que no deberían viajar (.env, *.db/*.sqlite, claves, exports, carpetas "persist" de alora.json) responde review_needed con la lista y NO envía nada — miralos y reenviá con confirm=true solo si son parte legítima del código. La copia aparece sola en el dashboard y list_projects de la otra persona como proyecto propio, llamada <proyecto>-<usuario-del-email> salvo que pases name: al instante si ya tiene cuenta verificada, o cuando cree una con ese email (7 días).',
|
|
242
|
+
inputSchema: {
|
|
243
|
+
project: z.string(),
|
|
244
|
+
email: z.string().describe('Email real de quien recibe la copia (preguntáselo al humano)'),
|
|
245
|
+
name: z.string().optional().describe('Nombre del proyecto nuevo en la cuenta del otro (minúsculas, números, guiones). Default: <proyecto>-<usuario del email>'),
|
|
246
|
+
version: z.number().optional().describe('Versión a copiar. Default: la activa'),
|
|
247
|
+
confirm: z.boolean().optional().describe('true para reenviar después de un review_needed, habiendo mirado los archivos listados'),
|
|
248
|
+
},
|
|
249
|
+
}, wrap(({ project, email, name, version, confirm }) => client().sendCopy(project, { email, name, version, confirm })));
|
|
250
|
+
|
|
251
|
+
server.registerTool('project_clone', {
|
|
252
|
+
description: 'Copia un proyecto DENTRO de tu propia cuenta como proyecto nuevo e independiente (variante, staging, "la v2"): solo el código de una versión, sin datos, secrets, miembros ni crons. Inmediato.',
|
|
253
|
+
inputSchema: {
|
|
254
|
+
project: z.string(),
|
|
255
|
+
name: z.string().describe('Nombre del proyecto nuevo (tiene que estar libre)'),
|
|
256
|
+
version: z.number().optional().describe('Versión a copiar. Default: la activa'),
|
|
257
|
+
},
|
|
258
|
+
}, wrap(({ project, name, version }) => client().clone(project, name, { version })));
|
|
259
|
+
|
|
260
|
+
server.registerTool('pull', {
|
|
261
|
+
description: 'Baja el código de un proyecto (una versión; la activa por default) a un directorio local para modificarlo y volver a deployar. Es cómo se trabaja sobre una copia que le enviaron al humano, o cómo se recupera el código si el directorio local se perdió. Después: editar y deploy al mismo proyecto; project_status.copied_from dice qué le falta (secrets por nombre, IA, crons).',
|
|
262
|
+
inputSchema: {
|
|
263
|
+
project: z.string(),
|
|
264
|
+
dir: z.string().describe('Ruta absoluta del directorio destino (se crea; tiene que estar vacío salvo force=true)'),
|
|
265
|
+
version: z.number().optional(),
|
|
266
|
+
force: z.boolean().optional().describe('Extraer aunque el directorio tenga cosas (pisa archivos con el mismo nombre)'),
|
|
267
|
+
},
|
|
268
|
+
}, wrap(async ({ project, dir, version, force }) => {
|
|
269
|
+
if (existsSync(dir) && readdirSync(dir).length && !force) {
|
|
270
|
+
throw new AloraError({ error: 'dir_not_empty', message: `${dir} no está vacío.`, fix: 'Elegí otro directorio o pasá force=true para extraer encima.' }, 400);
|
|
271
|
+
}
|
|
272
|
+
const buf = await client().pull(project, { version });
|
|
273
|
+
mkdirSync(dir, { recursive: true });
|
|
274
|
+
const tmpFile = path.join(tmpdir(), `alora-pull-${Date.now()}.tgz`);
|
|
275
|
+
writeFileSync(tmpFile, buf);
|
|
276
|
+
try { await tar.x({ file: tmpFile, cwd: dir }); } finally { rmSync(tmpFile, { force: true }); }
|
|
277
|
+
const status = await client().status(project).catch(() => null);
|
|
278
|
+
return {
|
|
279
|
+
project, version: version || status?.active_version || null, dir, bytes: buf.length,
|
|
280
|
+
files: readdirSync(dir).length,
|
|
281
|
+
...(status?.copied_from ? { copied_from: status.copied_from } : {}),
|
|
282
|
+
note: `Código en ${dir}. Editá y volvé a subir con deploy (project "${project}", dir "${dir}").${status?.copied_from ? ' Este proyecto nació como copia: copied_from.needs lista los secrets (por nombre), IA y crons que el original tenía y hay que reconstruir.' : ''}`,
|
|
283
|
+
};
|
|
284
|
+
}));
|
|
285
|
+
|
|
239
286
|
server.registerTool('analytics_summary', {
|
|
240
287
|
description: 'El pulso del proyecto: requests, usuarios activos, errores 5xx, rutas más usadas y serie por día. Usalo para saber qué partes de la app se usan (y cuáles no) antes de iterar.',
|
|
241
288
|
inputSchema: { project: z.string(), period: z.enum(['24h', '7d', '30d']).optional() },
|