alora-mcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +34 -0
- package/src/index.js +183 -0
package/package.json
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "alora-mcp",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Servidor MCP de Alora — Claude Code, Codex, Cursor y cualquier cliente MCP",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"bin": {
|
|
7
|
+
"alora-mcp": "src/index.js"
|
|
8
|
+
},
|
|
9
|
+
"dependencies": {
|
|
10
|
+
"alora-sdk": "^0.1.0",
|
|
11
|
+
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
12
|
+
"tar": "^7.4.3",
|
|
13
|
+
"zod": "^3.24.0"
|
|
14
|
+
},
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"homepage": "https://alora.build",
|
|
17
|
+
"repository": {
|
|
18
|
+
"type": "git",
|
|
19
|
+
"url": "git+https://github.com/santideosdeman/alora.git"
|
|
20
|
+
},
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=20"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"mcp",
|
|
26
|
+
"alora",
|
|
27
|
+
"deploy",
|
|
28
|
+
"claude",
|
|
29
|
+
"codex"
|
|
30
|
+
],
|
|
31
|
+
"files": [
|
|
32
|
+
"src"
|
|
33
|
+
]
|
|
34
|
+
}
|
package/src/index.js
ADDED
|
@@ -0,0 +1,183 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
// Servidor MCP de Alora. Mismas capacidades que el CLI, expuestas como tools
|
|
3
|
+
// para Claude Code, Codex, Cursor o cualquier cliente MCP.
|
|
4
|
+
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
5
|
+
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { readFileSync, rmSync } from 'node:fs';
|
|
8
|
+
import { homedir, tmpdir } from 'node:os';
|
|
9
|
+
import path from 'node:path';
|
|
10
|
+
import * as tar from 'tar';
|
|
11
|
+
import { AloraClient, AloraError } from 'alora-sdk';
|
|
12
|
+
|
|
13
|
+
const CONFIG_FILE = path.join(process.env.ALORA_HOME || path.join(homedir(), '.alora'), 'config.json');
|
|
14
|
+
|
|
15
|
+
function client() {
|
|
16
|
+
let cfg = {};
|
|
17
|
+
try { cfg = JSON.parse(readFileSync(CONFIG_FILE, 'utf8')); } catch {}
|
|
18
|
+
return new AloraClient({ api: cfg.api || 'https://api.alora.build', token: cfg.token });
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const server = new McpServer({ name: 'alora', version: '0.1.0' });
|
|
22
|
+
|
|
23
|
+
const ok = (data) => ({ content: [{ type: 'text', text: JSON.stringify(data, null, 2) }] });
|
|
24
|
+
const asError = (e) => ({
|
|
25
|
+
isError: true,
|
|
26
|
+
content: [{
|
|
27
|
+
type: 'text',
|
|
28
|
+
text: JSON.stringify(
|
|
29
|
+
e instanceof AloraError
|
|
30
|
+
? { error: e.code, message: e.message, fix: e.fix }
|
|
31
|
+
: { error: 'unknown', message: e.message, fix: 'Verificá que el servidor de Alora esté corriendo y que hayas hecho alora connect.' },
|
|
32
|
+
null, 2
|
|
33
|
+
),
|
|
34
|
+
}],
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
const wrap = (fn) => async (args) => {
|
|
38
|
+
try { return ok(await fn(args)); } catch (e) { return asError(e); }
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
server.registerTool('whoami', {
|
|
42
|
+
description: 'Identidad de este agente y su workspace en Alora.',
|
|
43
|
+
inputSchema: {},
|
|
44
|
+
}, wrap(() => client().whoami()));
|
|
45
|
+
|
|
46
|
+
server.registerTool('list_projects', {
|
|
47
|
+
description: 'Lista los proyectos del workspace con URL, versión activa y estado.',
|
|
48
|
+
inputSchema: {},
|
|
49
|
+
}, wrap(() => client().listProjects()));
|
|
50
|
+
|
|
51
|
+
server.registerTool('create_project', {
|
|
52
|
+
description: 'Crea un proyecto nuevo (una app con su URL propia). El nombre: minúsculas, números y guiones.',
|
|
53
|
+
inputSchema: { name: z.string().describe('Nombre del proyecto, ej: "mi-app"') },
|
|
54
|
+
}, wrap(({ name }) => client().createProject(name)));
|
|
55
|
+
|
|
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.',
|
|
58
|
+
inputSchema: {
|
|
59
|
+
project: z.string().describe('Nombre del proyecto'),
|
|
60
|
+
dir: z.string().describe('Ruta absoluta del directorio a desplegar'),
|
|
61
|
+
message: z.string().optional().describe('Mensaje corto describiendo el cambio'),
|
|
62
|
+
},
|
|
63
|
+
}, wrap(async ({ project, dir, message }) => {
|
|
64
|
+
const tmpFile = path.join(tmpdir(), `alora-mcp-${Date.now()}.tgz`);
|
|
65
|
+
await tar.create(
|
|
66
|
+
{
|
|
67
|
+
gzip: true, file: tmpFile, cwd: dir,
|
|
68
|
+
filter: (p) => !p.split(path.sep).some((seg) => ['node_modules', '.git', 'data', '.DS_Store'].includes(seg)),
|
|
69
|
+
},
|
|
70
|
+
['.']
|
|
71
|
+
);
|
|
72
|
+
const buffer = readFileSync(tmpFile);
|
|
73
|
+
rmSync(tmpFile, { force: true });
|
|
74
|
+
return client().deploy(project, buffer, { message });
|
|
75
|
+
}));
|
|
76
|
+
|
|
77
|
+
server.registerTool('project_status', {
|
|
78
|
+
description: 'Estado de un proyecto: versión activa, si está corriendo, URL.',
|
|
79
|
+
inputSchema: { project: z.string() },
|
|
80
|
+
}, wrap(({ project }) => client().status(project)));
|
|
81
|
+
|
|
82
|
+
server.registerTool('logs', {
|
|
83
|
+
description: 'Últimas líneas de log del proyecto (deploys, runtime y salida de la app). Usalo para debuggear tus propios deploys.',
|
|
84
|
+
inputSchema: {
|
|
85
|
+
project: z.string(),
|
|
86
|
+
lines: z.number().optional().describe('Cantidad de líneas, default 100'),
|
|
87
|
+
},
|
|
88
|
+
}, wrap(({ project, lines }) => client().logs(project, { lines })));
|
|
89
|
+
|
|
90
|
+
server.registerTool('list_versions', {
|
|
91
|
+
description: 'Historial de versiones del proyecto, con autor y mensaje.',
|
|
92
|
+
inputSchema: { project: z.string() },
|
|
93
|
+
}, wrap(({ project }) => client().listVersions(project)));
|
|
94
|
+
|
|
95
|
+
server.registerTool('rollback', {
|
|
96
|
+
description: 'Vuelve el proyecto a una versión anterior. El rollback también queda como historial.',
|
|
97
|
+
inputSchema: {
|
|
98
|
+
project: z.string(),
|
|
99
|
+
version: z.number().describe('Número de versión, ej: 3'),
|
|
100
|
+
},
|
|
101
|
+
}, wrap(({ project, version }) => client().rollback(project, version)));
|
|
102
|
+
|
|
103
|
+
server.registerTool('share', {
|
|
104
|
+
description: 'Invita personas a un proyecto por email, con rol. Nacen privados: solo los miembros pueden entrar (login puesto por Alora, la app no necesita código de auth). La app recibe x-alora-user-email / -name / -role en cada request.',
|
|
105
|
+
inputSchema: {
|
|
106
|
+
project: z.string(),
|
|
107
|
+
emails: z.array(z.string()).describe('Emails a invitar'),
|
|
108
|
+
role: z.enum(['admin', 'user', 'viewer']).optional().describe('Default: user'),
|
|
109
|
+
},
|
|
110
|
+
}, wrap(({ project, emails, role }) => client().share(project, emails, role || 'user')));
|
|
111
|
+
|
|
112
|
+
server.registerTool('list_members', {
|
|
113
|
+
description: 'Quién tiene acceso a un proyecto: dueño, miembros con rol y última visita, y visibilidad.',
|
|
114
|
+
inputSchema: { project: z.string() },
|
|
115
|
+
}, wrap(({ project }) => client().listMembers(project)));
|
|
116
|
+
|
|
117
|
+
server.registerTool('revoke_access', {
|
|
118
|
+
description: 'Le saca el acceso a una persona al instante.',
|
|
119
|
+
inputSchema: { project: z.string(), email: z.string() },
|
|
120
|
+
}, wrap(({ project, email }) => client().revokeAccess(project, email)));
|
|
121
|
+
|
|
122
|
+
server.registerTool('set_visibility', {
|
|
123
|
+
description: 'Cambia la visibilidad del proyecto: "private" (solo miembros, con login) o "public" (cualquiera con el link, sin login).',
|
|
124
|
+
inputSchema: { project: z.string(), visibility: z.enum(['private', 'public']) },
|
|
125
|
+
}, wrap(({ project, visibility }) => client().setVisibility(project, visibility)));
|
|
126
|
+
|
|
127
|
+
server.registerTool('analytics_summary', {
|
|
128
|
+
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.',
|
|
129
|
+
inputSchema: { project: z.string(), period: z.enum(['24h', '7d', '30d']).optional() },
|
|
130
|
+
}, wrap(({ project, period }) => client().analytics(project, period)));
|
|
131
|
+
|
|
132
|
+
server.registerTool('analytics_users', {
|
|
133
|
+
description: 'Actividad por persona: requests, primera y última visita de cada usuario autenticado.',
|
|
134
|
+
inputSchema: { project: z.string(), period: z.enum(['24h', '7d', '30d']).optional() },
|
|
135
|
+
}, wrap(({ project, period }) => client().analyticsUsers(project, period)));
|
|
136
|
+
|
|
137
|
+
server.registerTool('changelog', {
|
|
138
|
+
description: 'Historia del proyecto: versiones con autor y mensaje, más acciones (shares, rollbacks, secrets) del audit log.',
|
|
139
|
+
inputSchema: { project: z.string() },
|
|
140
|
+
}, wrap(({ project }) => client().changelog(project)));
|
|
141
|
+
|
|
142
|
+
server.registerTool('secrets_set', {
|
|
143
|
+
description: 'Guarda secrets del proyecto (cifrados). Se inyectan como variables de entorno y la app se reinicia con los valores nuevos. Nombres: MAYUSCULAS_CON_GUION_BAJO.',
|
|
144
|
+
inputSchema: { project: z.string(), secrets: z.record(z.string()).describe('{"STRIPE_KEY": "sk_..."}') },
|
|
145
|
+
}, wrap(({ project, secrets }) => client().secretsSet(project, secrets)));
|
|
146
|
+
|
|
147
|
+
server.registerTool('secrets_list', {
|
|
148
|
+
description: 'Lista los nombres de los secrets del proyecto (los valores nunca se devuelven).',
|
|
149
|
+
inputSchema: { project: z.string() },
|
|
150
|
+
}, wrap(({ project }) => client().secretsList(project)));
|
|
151
|
+
|
|
152
|
+
server.registerTool('ai_status', {
|
|
153
|
+
description: 'Estado del BYO Claude del workspace: si hay una cuenta de Claude conectada (suscripción o API key) y qué proyectos tienen IA habilitada. Las apps con IA pueden ejecutar `claude -p "prompt"` con la cuenta del dueño — pensado para proyectos personales o de poco uso.',
|
|
154
|
+
inputSchema: {},
|
|
155
|
+
}, wrap(() => client().aiStatus()));
|
|
156
|
+
|
|
157
|
+
server.registerTool('ai_enable', {
|
|
158
|
+
description: 'Habilita IA en un proyecto (Node o Python): la microVM recibe la credencial de Claude del dueño y el CLI `claude` queda en el PATH — la app puede ejecutar `claude -p "..." --output-format json` (usá child_process/subprocess). Requiere cuenta conectada (el humano corre `claude setup-token` + `alora ai connect` una vez). El primer arranque instala el CLI (~1 min); después es instantáneo. Con suscripción: solo para uso personal/bajo — el rate limit se comparte con el uso interactivo del dueño; para tráfico real conviene API key.',
|
|
159
|
+
inputSchema: { project: z.string() },
|
|
160
|
+
}, wrap(({ project }) => client().aiSetProject(project, true)));
|
|
161
|
+
|
|
162
|
+
server.registerTool('ai_disable', {
|
|
163
|
+
description: 'Deshabilita la IA en un proyecto: la app se reinicia sin la credencial de Claude.',
|
|
164
|
+
inputSchema: { project: z.string() },
|
|
165
|
+
}, wrap(({ project }) => client().aiSetProject(project, false)));
|
|
166
|
+
|
|
167
|
+
server.registerTool('wallet_balance', {
|
|
168
|
+
description: 'Saldo prepago del workspace y últimos movimientos del ledger. El saldo se carga en https://alora.build/wallet (checkout de Paddle) — el agente no puede cargar plata, solo el humano.',
|
|
169
|
+
inputSchema: {},
|
|
170
|
+
}, wrap(() => client().wallet()));
|
|
171
|
+
|
|
172
|
+
server.registerTool('project_trash', {
|
|
173
|
+
description: 'Manda un proyecto a la papelera: la URL deja de responder, restaurable por 30 días, después se purga solo. Nunca borra datos de inmediato.',
|
|
174
|
+
inputSchema: { project: z.string() },
|
|
175
|
+
}, wrap(({ project }) => client().trash(project)));
|
|
176
|
+
|
|
177
|
+
server.registerTool('project_restore', {
|
|
178
|
+
description: 'Restaura un proyecto de la papelera, intacto (código, datos, miembros).',
|
|
179
|
+
inputSchema: { project: z.string() },
|
|
180
|
+
}, wrap(({ project }) => client().restore(project)));
|
|
181
|
+
|
|
182
|
+
const transport = new StdioServerTransport();
|
|
183
|
+
await server.connect(transport);
|