alora-mcp 0.4.0 → 0.6.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.
Files changed (3) hide show
  1. package/README.md +62 -0
  2. package/package.json +3 -3
  3. package/src/index.js +49 -6
package/README.md ADDED
@@ -0,0 +1,62 @@
1
+ # alora-mcp
2
+
3
+ MCP server for [Alora](https://alora.build) — the cloud where your agent works.
4
+
5
+ Give your coding agent a place to **actually ship**: it deploys the apps it writes to real infrastructure, shares them by email, previews changes before production, runs 24/7 workers and cron jobs, and even buys domains (with your approval). You talk to the agent; the agent talks to Alora.
6
+
7
+ Works with Claude Code, Codex, Cursor, and any MCP client.
8
+
9
+ ## Install
10
+
11
+ ```bash
12
+ # Claude Code
13
+ claude mcp add alora -- npx alora-mcp
14
+
15
+ # Codex
16
+ codex mcp add alora -- npx alora-mcp
17
+ ```
18
+
19
+ Or in any MCP client's JSON config:
20
+
21
+ ```json
22
+ {
23
+ "mcpServers": {
24
+ "alora": { "command": "npx", "args": ["alora-mcp"] }
25
+ }
26
+ }
27
+ ```
28
+
29
+ ## Connect (once, takes a minute)
30
+
31
+ 1. Create a free account at [alora.build/register](https://alora.build/register) — $0.50 of credit included, no card.
32
+ 2. Run `npx alora-cli connect` in your terminal — it prints a code.
33
+ 3. Paste the code at [alora.build/activate](https://alora.build/activate).
34
+
35
+ 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).
36
+
37
+ ## What your agent can do (45 tools)
38
+
39
+ | | |
40
+ |---|---|
41
+ | **Ship** | `create_project` · `deploy` (Node, Python, static, or any command via Procfile/alora.json) · `list_versions` · `rollback` |
42
+ | **Preview before prod** | `preview` → temporary URL at `myapp--preview.alora.page` with throwaway data · `promote` · `preview_stop` |
43
+ | **Share** | apps are born private — `share` by email with roles, `set_visibility`, `revoke_access` |
44
+ | **Run things** | `set_always_on` / `set_worker_mode` for 24/7 processes · `cron_create` for scheduled hits · `exec` · `restart` · `logs` |
45
+ | **Configure** | `secrets_set` (encrypted, injected as env) · `domain_connect` · `egress_ip_allocate` (static egress IP) |
46
+ | **Use AI inside the app** | `ai_enable` — the deployed app runs `claude -p` with the owner's own Claude account |
47
+ | **Buy domains** | `domain_search` · `domain_buy` — always gated on the owner's approval by email |
48
+ | **Know what's happening** | `analytics_summary` · `analytics_users` · `wallet_balance` · `changelog` |
49
+
50
+ ## How it behaves
51
+
52
+ - **Apps are born private.** Visitors get a login page; the owner invites people by email. Your agent never writes auth code — identity arrives as trusted headers.
53
+ - **Spending has a hard ceiling.** Prepaid credits only: $0.01 per active hour, $0 while an app sleeps, $0.40/GB/month storage. An agent can't spend what you didn't load. Typical side project: under $1/month.
54
+ - **Irreversible actions ask a human.** Domain purchases require the owner to click an approval email.
55
+ - **Data persists.** Each app gets a data directory that survives deploys, restarts and sleep.
56
+
57
+ ## Links
58
+
59
+ - Website: [alora.build](https://alora.build) · [Pricing](https://alora.build/pricing) · [Blog](https://alora.build/blog)
60
+ - Agent docs: [alora.build/llms.txt](https://alora.build/llms.txt)
61
+ - CLI for humans: [`alora-cli`](https://www.npmjs.com/package/alora-cli) · API client: [`alora-sdk`](https://www.npmjs.com/package/alora-sdk)
62
+ - Feedback (agents welcome): the `send_feedback` tool, or hola@alora.build
package/package.json CHANGED
@@ -1,13 +1,13 @@
1
1
  {
2
2
  "name": "alora-mcp",
3
- "version": "0.4.0",
4
- "description": "Servidor MCP de Alora — Claude Code, Codex, Cursor y cualquier cliente MCP",
3
+ "version": "0.6.0",
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.4.0",
10
+ "alora-sdk": "^0.6.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
@@ -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.1.0' });
21
+ const server = new McpServer({ name: 'alora', version: '0.6.0' });
22
22
 
23
23
  const ok = (data) => ({ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] });
24
24
  const asError = (e) => ({
@@ -54,13 +54,19 @@ server.registerTool('create_project', {
54
54
  }, wrap(({ name }) => client().createProject(name)));
55
55
 
56
56
  server.registerTool('deploy', {
57
- description: 'Despliega un directorio local como nueva versión del proyecto. Detecta el runtime solo (Node, Python, estático). Devuelve la URL.',
57
+ description: 'Despliega un directorio local como nueva versión del proyecto. Detecta el runtime solo (Node, Python, estático). Arranque: "main"/server.js, `npm start`, un Procfile ("web: ...") o alora.json {"start": "cualquier comando"}. Frontends (Vite/Next/Astro…): pasá build (ej: "npm run build") y dir apuntando a la salida (dist/). Estáticos tienen fallback SPA a index.html. Devuelve la URL.',
58
58
  inputSchema: {
59
59
  project: z.string().describe('Nombre del proyecto'),
60
- dir: z.string().describe('Ruta absoluta del directorio a desplegar'),
60
+ dir: z.string().describe('Ruta absoluta del directorio a desplegar (para un frontend buildeado, la carpeta de salida: dist/, build/, out/)'),
61
61
  message: z.string().optional().describe('Mensaje corto describiendo el cambio'),
62
+ build: z.string().optional().describe('Comando a correr antes (en el directorio del proyecto), ej: "npm run build"'),
63
+ base_version: z.number().optional().describe('Versión de la que partiste: si otro agente desplegó mientras tanto, falla con version_conflict en vez de pisar su trabajo'),
62
64
  },
63
- }, wrap(async ({ project, dir, message }) => {
65
+ }, wrap(async ({ project, dir, message, build, base_version }) => {
66
+ if (build) {
67
+ const { execSync } = await import('node:child_process');
68
+ execSync(build, { cwd: dir, stdio: 'ignore' });
69
+ }
64
70
  const tmpFile = path.join(tmpdir(), `alora-mcp-${Date.now()}.tgz`);
65
71
  await tar.create(
66
72
  {
@@ -71,9 +77,41 @@ server.registerTool('deploy', {
71
77
  );
72
78
  const buffer = readFileSync(tmpFile);
73
79
  rmSync(tmpFile, { force: true });
74
- return client().deploy(project, buffer, { message });
80
+ return client().deploy(project, buffer, { message, base_version });
75
81
  }));
76
82
 
83
+ server.registerTool('preview', {
84
+ description: 'Despliega un candidato a una URL TEMPORAL (<proyecto>--preview) SIN tocar la versión viva. El loop con el humano: subís el cambio acá, le pasás el link para que lo mire, y recién si le gusta lo promovés a producción con `promote`. Datos efímeros (no comparte el /data de prod). Expira en 24h. Mismo runtime/detección/build que deploy. Devolvé preview_url al humano.',
85
+ inputSchema: {
86
+ project: z.string(),
87
+ dir: z.string().describe('Ruta absoluta del directorio a desplegar (para un frontend buildeado, la carpeta de salida)'),
88
+ message: z.string().optional(),
89
+ build: z.string().optional().describe('Comando a correr antes, ej: "npm run build"'),
90
+ },
91
+ }, wrap(async ({ project, dir, message, build }) => {
92
+ if (build) { const { execSync } = await import('node:child_process'); execSync(build, { cwd: dir, stdio: 'ignore' }); }
93
+ const tmpFile = path.join(tmpdir(), `alora-preview-${Date.now()}.tgz`);
94
+ await tar.create({ gzip: true, file: tmpFile, cwd: dir, filter: (p) => !p.split(path.sep).some((seg) => ['node_modules', '.git', 'data', '.DS_Store'].includes(seg)) }, ['.']);
95
+ const buffer = readFileSync(tmpFile);
96
+ rmSync(tmpFile, { force: true });
97
+ return client().preview(project, buffer, { message });
98
+ }));
99
+
100
+ server.registerTool('promote', {
101
+ description: 'Promueve a producción el preview del proyecto (o una versión puntual). Lo que estaba en la URL temporal pasa a ser la versión viva; el preview se baja. Usalo cuando el humano aprobó lo que le mostraste con `preview`.',
102
+ inputSchema: { project: z.string(), version: z.number().optional().describe('Versión a promover; por default, la del preview activo') },
103
+ }, wrap(({ project, version }) => client().promote(project, version)));
104
+
105
+ server.registerTool('preview_stop', {
106
+ description: 'Baja el preview de un proyecto (la URL temporal deja de responder). La app viva no se toca.',
107
+ inputSchema: { project: z.string() },
108
+ }, wrap(({ project }) => client().previewStop(project)));
109
+
110
+ server.registerTool('preview_status', {
111
+ description: 'Muestra si hay un preview activo, su URL temporal, la versión y cuándo expira.',
112
+ inputSchema: { project: z.string() },
113
+ }, wrap(({ project }) => client().previewInfo(project)));
114
+
77
115
  server.registerTool('project_status', {
78
116
  description: 'Estado de un proyecto: versión activa, si está corriendo, URL.',
79
117
  inputSchema: { project: z.string() },
@@ -109,10 +147,15 @@ server.registerTool('egress_ip', {
109
147
  }, wrap(({ project }) => client().egressIp(project)));
110
148
 
111
149
  server.registerTool('egress_ip_allocate', {
112
- description: 'Asigna una IP de egreso ESTÁTICA y dedicada al proyecto (sobrevive migraciones y reinicios) y reinicia la app para tomarla. Para apps que operan APIs con whitelist de IP: exchanges de cripto (Binance exige IP restringida para trading), bancos, brokers, SaaS enterprise. Whitelisteá la IP devuelta.',
150
+ description: 'Asigna una IP de egreso ESTÁTICA y dedicada al proyecto (add-on pago, ~US$4/mes debitado del saldo; primer mes al asignar y luego mensual). Sobrevive migraciones y reinicios; reinicia la app para tomarla. Para apps que operan APIs con whitelist de IP: exchanges de cripto (Binance exige IP restringida para trading), bancos, brokers, SaaS enterprise. Requiere saldo. Si el saldo no cubre la renovación, la IP se libera y se avisa. Whitelisteá la IP devuelta.',
113
151
  inputSchema: { project: z.string() },
114
152
  }, wrap(({ project }) => client().egressIpAllocate(project)));
115
153
 
154
+ server.registerTool('egress_ip_release', {
155
+ description: 'Libera la IP de egreso dedicada del proyecto y deja de cobrarla. La IP de egreso vuelve a ser compartida (puede cambiar).',
156
+ inputSchema: { project: z.string() },
157
+ }, wrap(({ project }) => client().egressIpRelease(project)));
158
+
116
159
  server.registerTool('token_create', {
117
160
  description: 'Crea un token de acceso por proyecto que BYPASSEA el login: un script/webhook/backup externo puede pegarle a la URL de la app con Authorization: Bearer <token> sin SSO. Scoped a este proyecto y revocable. La app lo ve con x-alora-user-role: service. El token se muestra una sola vez.',
118
161
  inputSchema: { project: z.string(), name: z.string().optional() },