pulsecloud-mcp 1.0.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/index.js ADDED
@@ -0,0 +1,833 @@
1
+ #!/usr/bin/env node
2
+ import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
+ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
+ import { CallToolRequestSchema, ListToolsRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
+ import { setToken, logout, isAuthenticated, getAccountEmail, getAuthUrl, startCallbackServer } from './auth.js';
6
+ import * as ptero from './services/pterodactyl.js';
7
+ import * as ctrlpanel from './services/ctrlpanel.js';
8
+ import * as statusPage from './services/status.js';
9
+ import * as npm from './services/npm.js';
10
+ import * as cf from './services/cloudflare.js';
11
+ const server = new Server({ name: 'pulsecloud-mcp', version: '1.0.0' }, { capabilities: { tools: {} } });
12
+ server.setRequestHandler(ListToolsRequestSchema, async () => ({
13
+ tools: [
14
+ // ─── AUTH ──────────────────────────────────────────────────────
15
+ {
16
+ name: 'pulsecloud_login',
17
+ description: 'Abre o navegador para autorizar o MCP no PulseCloud. Você faz login no site do PulseCloud e autoriza o acesso. Após autorizar, o token é salvo automaticamente.',
18
+ inputSchema: { type: 'object', properties: {} },
19
+ },
20
+ {
21
+ name: 'pulsecloud_login_token',
22
+ description: 'Salva um token de API do Pterodactyl (ptlc_...) que você já criou manualmente ou obteve em https://pulsecloud.com.br/mcp/authorize',
23
+ inputSchema: {
24
+ type: 'object',
25
+ properties: {
26
+ token: { type: 'string', description: 'Client API Token do Pterodactyl (ptlc_...)' },
27
+ email: { type: 'string', description: 'Email da conta (opcional)' },
28
+ },
29
+ required: ['token'],
30
+ },
31
+ },
32
+ {
33
+ name: 'pulsecloud_logout',
34
+ description: 'Remove o token de autenticação salvo localmente.',
35
+ inputSchema: { type: 'object', properties: {} },
36
+ },
37
+ {
38
+ name: 'pulsecloud_whoami',
39
+ description: 'Mostra informações da conta autenticada atualmente.',
40
+ inputSchema: { type: 'object', properties: {} },
41
+ },
42
+ // ─── STATUS (público) ──────────────────────────────────────────
43
+ {
44
+ name: 'pulsecloud_status',
45
+ description: 'Verifica o status público dos serviços PulseCloud. Não precisa de autenticação.',
46
+ inputSchema: { type: 'object', properties: {} },
47
+ },
48
+ // ─── SERVIDORES ────────────────────────────────────────────────
49
+ {
50
+ name: 'pulsecloud_server_list',
51
+ description: 'Lista todos os servidores da sua conta PulseCloud.',
52
+ inputSchema: { type: 'object', properties: {} },
53
+ },
54
+ {
55
+ name: 'pulsecloud_server_get',
56
+ description: 'Obtém detalhes completos de um servidor específico.',
57
+ inputSchema: {
58
+ type: 'object',
59
+ properties: {
60
+ server_id: { type: 'string', description: 'ID ou identificador do servidor' },
61
+ },
62
+ required: ['server_id'],
63
+ },
64
+ },
65
+ {
66
+ name: 'pulsecloud_server_resources',
67
+ description: 'Obtém o uso atual de recursos (CPU, RAM, disco) de um servidor em tempo real.',
68
+ inputSchema: {
69
+ type: 'object',
70
+ properties: {
71
+ server_id: { type: 'string', description: 'ID do servidor' },
72
+ },
73
+ required: ['server_id'],
74
+ },
75
+ },
76
+ {
77
+ name: 'pulsecloud_server_create',
78
+ description: 'Cria um novo servidor no PulseCloud via CtrlPanel.',
79
+ inputSchema: {
80
+ type: 'object',
81
+ properties: {
82
+ name: { type: 'string', description: 'Nome do servidor' },
83
+ product_id: { type: 'string', description: 'ID do produto/plano (opcional - veja pulsecloud_product_list)' },
84
+ description: { type: 'string', description: 'Descrição do servidor' },
85
+ egg_id: { type: 'number', description: 'ID do egg (tipo de servidor)' },
86
+ nest_id: { type: 'number', description: 'ID do nest' },
87
+ memory: { type: 'number', description: 'Memória em MB' },
88
+ cpu: { type: 'number', description: 'CPU em %' },
89
+ disk: { type: 'number', description: 'Disco em MB' },
90
+ },
91
+ },
92
+ },
93
+ {
94
+ name: 'pulsecloud_server_delete',
95
+ description: 'Deleta um servidor via CtrlPanel.',
96
+ inputSchema: {
97
+ type: 'object',
98
+ properties: {
99
+ server_id: { type: 'string', description: 'ID do servidor no CtrlPanel' },
100
+ },
101
+ required: ['server_id'],
102
+ },
103
+ },
104
+ {
105
+ name: 'pulsecloud_server_suspend',
106
+ description: 'Suspende um servidor via CtrlPanel.',
107
+ inputSchema: {
108
+ type: 'object',
109
+ properties: {
110
+ server_id: { type: 'string', description: 'ID do servidor no CtrlPanel' },
111
+ },
112
+ required: ['server_id'],
113
+ },
114
+ },
115
+ {
116
+ name: 'pulsecloud_server_unsuspend',
117
+ description: 'Remove a suspensão de um servidor via CtrlPanel.',
118
+ inputSchema: {
119
+ type: 'object',
120
+ properties: {
121
+ server_id: { type: 'string', description: 'ID do servidor no CtrlPanel' },
122
+ },
123
+ required: ['server_id'],
124
+ },
125
+ },
126
+ {
127
+ name: 'pulsecloud_product_list',
128
+ description: 'Lista os produtos/planos disponíveis no CtrlPanel para criar servidores.',
129
+ inputSchema: { type: 'object', properties: {} },
130
+ },
131
+ // ─── POWER ACTIONS ────────────────────────────────────────────
132
+ {
133
+ name: 'pulsecloud_server_start',
134
+ description: 'Inicia um servidor Pterodactyl.',
135
+ inputSchema: {
136
+ type: 'object',
137
+ properties: {
138
+ server_id: { type: 'string', description: 'ID do servidor Pterodactyl' },
139
+ },
140
+ required: ['server_id'],
141
+ },
142
+ },
143
+ {
144
+ name: 'pulsecloud_server_stop',
145
+ description: 'Para um servidor Pterodactyl.',
146
+ inputSchema: {
147
+ type: 'object',
148
+ properties: {
149
+ server_id: { type: 'string', description: 'ID do servidor' },
150
+ },
151
+ required: ['server_id'],
152
+ },
153
+ },
154
+ {
155
+ name: 'pulsecloud_server_restart',
156
+ description: 'Reinicia um servidor Pterodactyl.',
157
+ inputSchema: {
158
+ type: 'object',
159
+ properties: {
160
+ server_id: { type: 'string', description: 'ID do servidor' },
161
+ },
162
+ required: ['server_id'],
163
+ },
164
+ },
165
+ {
166
+ name: 'pulsecloud_server_kill',
167
+ description: 'Mata (força parada) de um servidor Pterodactyl.',
168
+ inputSchema: {
169
+ type: 'object',
170
+ properties: {
171
+ server_id: { type: 'string', description: 'ID do servidor' },
172
+ },
173
+ required: ['server_id'],
174
+ },
175
+ },
176
+ {
177
+ name: 'pulsecloud_server_command',
178
+ description: 'Envia um comando no console do servidor.',
179
+ inputSchema: {
180
+ type: 'object',
181
+ properties: {
182
+ server_id: { type: 'string', description: 'ID do servidor' },
183
+ command: { type: 'string', description: 'Comando para executar' },
184
+ },
185
+ required: ['server_id', 'command'],
186
+ },
187
+ },
188
+ {
189
+ name: 'pulsecloud_server_logs',
190
+ description: 'Obtém os logs recentes do servidor.',
191
+ inputSchema: {
192
+ type: 'object',
193
+ properties: {
194
+ server_id: { type: 'string', description: 'ID do servidor' },
195
+ },
196
+ required: ['server_id'],
197
+ },
198
+ },
199
+ // ─── ARQUIVOS ─────────────────────────────────────────────────
200
+ {
201
+ name: 'pulsecloud_files_list',
202
+ description: 'Lista arquivos e pastas de um diretório no servidor.',
203
+ inputSchema: {
204
+ type: 'object',
205
+ properties: {
206
+ server_id: { type: 'string', description: 'ID do servidor' },
207
+ directory: { type: 'string', description: 'Caminho do diretório', default: '/' },
208
+ },
209
+ required: ['server_id'],
210
+ },
211
+ },
212
+ {
213
+ name: 'pulsecloud_file_read',
214
+ description: 'Lê o conteúdo de um arquivo no servidor.',
215
+ inputSchema: {
216
+ type: 'object',
217
+ properties: {
218
+ server_id: { type: 'string', description: 'ID do servidor' },
219
+ file: { type: 'string', description: 'Caminho completo do arquivo' },
220
+ },
221
+ required: ['server_id', 'file'],
222
+ },
223
+ },
224
+ {
225
+ name: 'pulsecloud_file_write',
226
+ description: 'Cria ou sobrescreve um arquivo no servidor.',
227
+ inputSchema: {
228
+ type: 'object',
229
+ properties: {
230
+ server_id: { type: 'string', description: 'ID do servidor' },
231
+ file: { type: 'string', description: 'Caminho completo do arquivo' },
232
+ content: { type: 'string', description: 'Conteúdo do arquivo' },
233
+ },
234
+ required: ['server_id', 'file', 'content'],
235
+ },
236
+ },
237
+ {
238
+ name: 'pulsecloud_file_delete',
239
+ description: 'Deleta arquivos ou pastas do servidor.',
240
+ inputSchema: {
241
+ type: 'object',
242
+ properties: {
243
+ server_id: { type: 'string', description: 'ID do servidor' },
244
+ files: { type: 'array', items: { type: 'string' }, description: 'Lista de caminhos' },
245
+ },
246
+ required: ['server_id', 'files'],
247
+ },
248
+ },
249
+ {
250
+ name: 'pulsecloud_file_rename',
251
+ description: 'Renomeia ou move um arquivo/pasta.',
252
+ inputSchema: {
253
+ type: 'object',
254
+ properties: {
255
+ server_id: { type: 'string', description: 'ID do servidor' },
256
+ from: { type: 'string', description: 'Caminho original' },
257
+ to: { type: 'string', description: 'Novo caminho' },
258
+ },
259
+ required: ['server_id', 'from', 'to'],
260
+ },
261
+ },
262
+ {
263
+ name: 'pulsecloud_file_compress',
264
+ description: 'Comprime arquivos em um archive (.tar.gz).',
265
+ inputSchema: {
266
+ type: 'object',
267
+ properties: {
268
+ server_id: { type: 'string', description: 'ID do servidor' },
269
+ files: { type: 'array', items: { type: 'string' }, description: 'Lista de arquivos' },
270
+ },
271
+ required: ['server_id', 'files'],
272
+ },
273
+ },
274
+ {
275
+ name: 'pulsecloud_file_decompress',
276
+ description: 'Descomprime um archive (.tar.gz) no servidor.',
277
+ inputSchema: {
278
+ type: 'object',
279
+ properties: {
280
+ server_id: { type: 'string', description: 'ID do servidor' },
281
+ file: { type: 'string', description: 'Caminho do archive' },
282
+ },
283
+ required: ['server_id', 'file'],
284
+ },
285
+ },
286
+ {
287
+ name: 'pulsecloud_file_download_url',
288
+ description: 'Gera uma URL temporária para download de um arquivo do servidor.',
289
+ inputSchema: {
290
+ type: 'object',
291
+ properties: {
292
+ server_id: { type: 'string', description: 'ID do servidor' },
293
+ file: { type: 'string', description: 'Caminho do arquivo' },
294
+ },
295
+ required: ['server_id', 'file'],
296
+ },
297
+ },
298
+ {
299
+ name: 'pulsecloud_file_create_directory',
300
+ description: 'Cria uma nova pasta no servidor.',
301
+ inputSchema: {
302
+ type: 'object',
303
+ properties: {
304
+ server_id: { type: 'string', description: 'ID do servidor' },
305
+ name: { type: 'string', description: 'Nome da pasta' },
306
+ },
307
+ required: ['server_id', 'name'],
308
+ },
309
+ },
310
+ // ─── BACKUPS ──────────────────────────────────────────────────
311
+ {
312
+ name: 'pulsecloud_backup_list',
313
+ description: 'Lista todos os backups de um servidor.',
314
+ inputSchema: {
315
+ type: 'object',
316
+ properties: {
317
+ server_id: { type: 'string', description: 'ID do servidor' },
318
+ },
319
+ required: ['server_id'],
320
+ },
321
+ },
322
+ {
323
+ name: 'pulsecloud_backup_create',
324
+ description: 'Cria um novo backup do servidor.',
325
+ inputSchema: {
326
+ type: 'object',
327
+ properties: {
328
+ server_id: { type: 'string', description: 'ID do servidor' },
329
+ name: { type: 'string', description: 'Nome opcional para o backup' },
330
+ },
331
+ required: ['server_id'],
332
+ },
333
+ },
334
+ {
335
+ name: 'pulsecloud_backup_download_url',
336
+ description: 'Obtém a URL de download de um backup.',
337
+ inputSchema: {
338
+ type: 'object',
339
+ properties: {
340
+ server_id: { type: 'string', description: 'ID do servidor' },
341
+ backup_id: { type: 'string', description: 'ID do backup' },
342
+ },
343
+ required: ['server_id', 'backup_id'],
344
+ },
345
+ },
346
+ {
347
+ name: 'pulsecloud_backup_delete',
348
+ description: 'Deleta um backup.',
349
+ inputSchema: {
350
+ type: 'object',
351
+ properties: {
352
+ server_id: { type: 'string', description: 'ID do servidor' },
353
+ backup_id: { type: 'string', description: 'ID do backup' },
354
+ },
355
+ required: ['server_id', 'backup_id'],
356
+ },
357
+ },
358
+ // ─── DATABASES ────────────────────────────────────────────────
359
+ {
360
+ name: 'pulsecloud_database_list',
361
+ description: 'Lista os bancos de dados de um servidor.',
362
+ inputSchema: {
363
+ type: 'object',
364
+ properties: {
365
+ server_id: { type: 'string', description: 'ID do servidor' },
366
+ },
367
+ required: ['server_id'],
368
+ },
369
+ },
370
+ {
371
+ name: 'pulsecloud_database_create',
372
+ description: 'Cria um novo banco de dados no servidor.',
373
+ inputSchema: {
374
+ type: 'object',
375
+ properties: {
376
+ server_id: { type: 'string', description: 'ID do servidor' },
377
+ database_name: { type: 'string', description: 'Nome do banco de dados' },
378
+ },
379
+ required: ['server_id', 'database_name'],
380
+ },
381
+ },
382
+ {
383
+ name: 'pulsecloud_database_delete',
384
+ description: 'Remove um banco de dados do servidor.',
385
+ inputSchema: {
386
+ type: 'object',
387
+ properties: {
388
+ server_id: { type: 'string', description: 'ID do servidor' },
389
+ database_id: { type: 'string', description: 'ID do banco de dados' },
390
+ },
391
+ required: ['server_id', 'database_id'],
392
+ },
393
+ },
394
+ {
395
+ name: 'pulsecloud_database_rotate_password',
396
+ description: 'Rotaciona (gera nova) senha de um banco de dados.',
397
+ inputSchema: {
398
+ type: 'object',
399
+ properties: {
400
+ server_id: { type: 'string', description: 'ID do servidor' },
401
+ database_id: { type: 'string', description: 'ID do banco de dados' },
402
+ },
403
+ required: ['server_id', 'database_id'],
404
+ },
405
+ },
406
+ // ─── ALLOCATIONS / NETWORK ───────────────────────────────────
407
+ {
408
+ name: 'pulsecloud_server_allocations',
409
+ description: 'Lista as alocações de rede (portas/IPs) de um servidor.',
410
+ inputSchema: {
411
+ type: 'object',
412
+ properties: {
413
+ server_id: { type: 'string', description: 'ID do servidor' },
414
+ },
415
+ required: ['server_id'],
416
+ },
417
+ },
418
+ {
419
+ name: 'pulsecloud_server_set_primary_allocation',
420
+ description: 'Define qual alocação é a primária de um servidor.',
421
+ inputSchema: {
422
+ type: 'object',
423
+ properties: {
424
+ server_id: { type: 'string', description: 'ID do servidor' },
425
+ allocation_id: { type: 'number', description: 'ID da alocação' },
426
+ },
427
+ required: ['server_id', 'allocation_id'],
428
+ },
429
+ },
430
+ // ─── SUBDOMÍNIOS (NPM) ───────────────────────────────────────
431
+ {
432
+ name: 'pulsecloud_subdomain_list',
433
+ description: 'Lista todos os proxy hosts configurados no Nginx Proxy Manager.',
434
+ inputSchema: { type: 'object', properties: {} },
435
+ },
436
+ {
437
+ name: 'pulsecloud_subdomain_create',
438
+ description: 'Cria um novo subdomínio apontando para um IP e porta.',
439
+ inputSchema: {
440
+ type: 'object',
441
+ properties: {
442
+ domain: { type: 'string', description: 'Domínio completo (ex: meusite.pulsecloud.com.br)' },
443
+ forward_host: { type: 'string', description: 'IP para redirecionar' },
444
+ forward_port: { type: 'number', description: 'Porta para redirecionar' },
445
+ forward_scheme: { type: 'string', enum: ['http', 'https'], default: 'http' },
446
+ ssl_forced: { type: 'boolean', default: true },
447
+ create_dns: { type: 'boolean', default: true },
448
+ ssl_email: { type: 'string', description: 'Email para certificado SSL' },
449
+ },
450
+ required: ['domain', 'forward_host', 'forward_port'],
451
+ },
452
+ },
453
+ {
454
+ name: 'pulsecloud_subdomain_delete',
455
+ description: 'Remove um proxy host do NPM e opcionalmente o DNS na Cloudflare.',
456
+ inputSchema: {
457
+ type: 'object',
458
+ properties: {
459
+ proxy_id: { type: 'number', description: 'ID do proxy host no NPM' },
460
+ delete_dns: { type: 'boolean', default: false },
461
+ },
462
+ required: ['proxy_id'],
463
+ },
464
+ },
465
+ ],
466
+ }));
467
+ server.setRequestHandler(CallToolRequestSchema, async (request) => {
468
+ const { name, arguments: args } = request.params;
469
+ try {
470
+ switch (name) {
471
+ // ─── AUTH ──────────────────────────────────────────────────
472
+ case 'pulsecloud_login': {
473
+ const authUrl = getAuthUrl();
474
+ try {
475
+ const cb = await startCallbackServer();
476
+ const fullUrl = `${authUrl}?redirect=${encodeURIComponent(cb.url)}`;
477
+ return {
478
+ content: [{
479
+ type: 'text',
480
+ text: `## 🔑 Autorizar PulseCloud MCP\n\n1. Abra o link abaixo no seu navegador:\n **[${fullUrl}](${fullUrl})**\n\n2. Faça login no PulseCloud (se necessário)\n3. Clique em **Autorizar**\n4. O token será salvo automaticamente!\n\n*Aguardando autorização...*`,
481
+ }],
482
+ };
483
+ }
484
+ catch {
485
+ return {
486
+ content: [{
487
+ type: 'text',
488
+ text: `## 🔑 Autorizar PulseCloud MCP\n\nAbra o link abaixo no seu navegador:\n\n**[${authUrl}](${authUrl})**\n\nApós autorizar, copie o token e use **pulsecloud_login_token** para salvá-lo.`,
489
+ }],
490
+ };
491
+ }
492
+ }
493
+ case 'pulsecloud_login_token': {
494
+ const { token, email } = args;
495
+ setToken(email || 'pulsecloud-user', token);
496
+ return {
497
+ content: [{ type: 'text', text: `✅ Token salvo com sucesso em \`~/.pulsecloud-mcp/auth.json\`` }],
498
+ };
499
+ }
500
+ case 'pulsecloud_logout': {
501
+ const email = getAccountEmail();
502
+ logout();
503
+ return {
504
+ content: [{ type: 'text', text: `🔓 Sessão encerrada${email ? ` para **${email}**` : ''}.` }],
505
+ };
506
+ }
507
+ case 'pulsecloud_whoami': {
508
+ if (!isAuthenticated()) {
509
+ return { content: [{ type: 'text', text: '❌ Não autenticado. Acesse https://pulsecloud.com.br/mcp/authorize para autorizar.' }] };
510
+ }
511
+ const email = getAccountEmail();
512
+ let accountInfo = '';
513
+ try {
514
+ const info = await ptero.getAccountInfo();
515
+ accountInfo = `\n\`\`\`json\n${JSON.stringify(info, null, 2)}\n\`\`\``;
516
+ }
517
+ catch { }
518
+ return {
519
+ content: [{ type: 'text', text: `👤 **${email}**${accountInfo}` }],
520
+ };
521
+ }
522
+ // ─── STATUS ────────────────────────────────────────────────
523
+ case 'pulsecloud_status': {
524
+ const status = await statusPage.getStatus();
525
+ const components = status.components.map(c => ` - **${c.name}**: ${statusEmoji(c.status)} ${c.status}`).join('\n');
526
+ return {
527
+ content: [{
528
+ type: 'text',
529
+ text: `## 📊 Status PulseCloud\n\n**Geral:** ${statusEmoji(status.overall)} ${status.overall}\n\n**Componentes:**\n${components}\n\n🕐 ${status.last_updated || 'N/A'}`,
530
+ }],
531
+ };
532
+ }
533
+ // ─── SERVIDORES ────────────────────────────────────────────
534
+ case 'pulsecloud_server_list': {
535
+ reqAuth();
536
+ const servers = await ptero.listServers();
537
+ if (servers.length === 0) {
538
+ return { content: [{ type: 'text', text: '📭 Nenhum servidor encontrado.' }] };
539
+ }
540
+ const lines = servers.map(s => ` - **${s.name}** (\`${s.id}\`) — ${s.installed ? (s.suspended ? '🔒 Suspenso' : '✅ Ativo') : '⏳ Instalando'} | ${formatMB(s.limits.memory)} RAM, ${s.limits.cpu}% CPU`).join('\n');
541
+ return {
542
+ content: [{ type: 'text', text: `## 🖥️ Seus Servidores (${servers.length})\n\n${lines}` }],
543
+ };
544
+ }
545
+ case 'pulsecloud_server_get': {
546
+ reqAuth();
547
+ const { server_id } = args;
548
+ const serverData = await ptero.getServer(server_id);
549
+ return {
550
+ content: [{ type: 'text', text: `## 🖥️ ${serverData.name}\n\n\`\`\`json\n${JSON.stringify(serverData, null, 2)}\n\`\`\`` }],
551
+ };
552
+ }
553
+ case 'pulsecloud_server_resources': {
554
+ reqAuth();
555
+ const { server_id } = args;
556
+ const res = await ptero.getServerResources(server_id);
557
+ return {
558
+ content: [{ type: 'text', text: `## 📈 Recursos - ${server_id}\n\n\`\`\`json\n${JSON.stringify(res, null, 2)}\n\`\`\`` }],
559
+ };
560
+ }
561
+ case 'pulsecloud_server_create': {
562
+ reqAuth();
563
+ const params = args;
564
+ const result = await ctrlpanel.createServer({
565
+ name: params.name,
566
+ product_id: params.product_id,
567
+ description: params.description,
568
+ egg_id: params.egg_id,
569
+ nest_id: params.nest_id,
570
+ memory: params.memory,
571
+ cpu: params.cpu,
572
+ disk: params.disk,
573
+ });
574
+ return {
575
+ content: [{ type: 'text', text: `✅ Servidor criado!\n\n\`\`\`json\n${JSON.stringify(result, null, 2)}\n\`\`\`` }],
576
+ };
577
+ }
578
+ case 'pulsecloud_server_delete':
579
+ case 'pulsecloud_server_suspend':
580
+ case 'pulsecloud_server_unsuspend': {
581
+ reqAuth();
582
+ const { server_id } = args;
583
+ const fn = name === 'pulsecloud_server_delete' ? ctrlpanel.deleteServer
584
+ : name === 'pulsecloud_server_suspend' ? ctrlpanel.suspendServer
585
+ : ctrlpanel.unsuspendServer;
586
+ await fn(server_id);
587
+ const action = name === 'pulsecloud_server_delete' ? 'deletado' : name === 'pulsecloud_server_suspend' ? 'suspenso' : 'não está mais suspenso';
588
+ return { content: [{ type: 'text', text: `✅ Servidor ${server_id} ${action}.` }] };
589
+ }
590
+ case 'pulsecloud_product_list': {
591
+ reqAuth();
592
+ const products = await ctrlpanel.listProducts();
593
+ const lines = products.map((p) => ` - **${p.name}** — R$ ${p.price} | ${formatBytes(p.memory * 1024 * 1024)} RAM, ${p.cpu}% CPU, ${formatBytes(p.disk * 1024 * 1024)} SSD`).join('\n');
594
+ return { content: [{ type: 'text', text: `## 📦 Produtos Disponíveis\n\n${lines}` }] };
595
+ }
596
+ // ─── POWER ACTIONS ─────────────────────────────────────────
597
+ case 'pulsecloud_server_start':
598
+ case 'pulsecloud_server_stop':
599
+ case 'pulsecloud_server_restart':
600
+ case 'pulsecloud_server_kill': {
601
+ reqAuth();
602
+ const { server_id } = args;
603
+ const action = name.split('_').pop();
604
+ await ptero.powerAction(server_id, action);
605
+ const msgs = { start: '▶️ Iniciando...', stop: '⏹️ Parando...', restart: '🔄 Reiniciando...', kill: '💀 Kill forçado.' };
606
+ return { content: [{ type: 'text', text: `${msgs[action]} Servidor \`${server_id}\`` }] };
607
+ }
608
+ case 'pulsecloud_server_command': {
609
+ reqAuth();
610
+ const { server_id, command } = args;
611
+ await ptero.sendCommand(server_id, command);
612
+ return { content: [{ type: 'text', text: `⌨️ Comando enviado para \`${server_id}\`:\n\`\`\`\n${command}\n\`\`\`` }] };
613
+ }
614
+ case 'pulsecloud_server_logs': {
615
+ reqAuth();
616
+ const { server_id } = args;
617
+ const logs = await ptero.getServerLogs(server_id);
618
+ return { content: [{ type: 'text', text: logs || '(sem logs no momento)' }] };
619
+ }
620
+ // ─── ARQUIVOS ──────────────────────────────────────────────
621
+ case 'pulsecloud_files_list': {
622
+ reqAuth();
623
+ const { server_id, directory = '/' } = args;
624
+ const files = await ptero.listFiles(server_id, directory);
625
+ if (!files || files.length === 0) {
626
+ return { content: [{ type: 'text', text: `📂 Diretório vazio: \`${directory}\`` }] };
627
+ }
628
+ const lines = files.map((f) => ` ${f.is_file ? '📄' : '📁'} **${f.name}**${f.is_file ? ` (${formatBytes(f.size)})` : '/'}`).join('\n');
629
+ return { content: [{ type: 'text', text: `## 📂 \`${directory}\`\n\n${lines}` }] };
630
+ }
631
+ case 'pulsecloud_file_read': {
632
+ reqAuth();
633
+ const { server_id, file } = args;
634
+ const content = await ptero.readFile(server_id, file);
635
+ return { content: [{ type: 'text', text: `📄 \`${file}\`:\n\`\`\`\n${content}\n\`\`\`` }] };
636
+ }
637
+ case 'pulsecloud_file_write': {
638
+ reqAuth();
639
+ const { server_id, file, content } = args;
640
+ await ptero.writeFile(server_id, file, content);
641
+ return { content: [{ type: 'text', text: `✅ Arquivo \`${file}\` salvo.` }] };
642
+ }
643
+ case 'pulsecloud_file_delete': {
644
+ reqAuth();
645
+ const { server_id, files } = args;
646
+ await ptero.deleteFiles(server_id, files);
647
+ return { content: [{ type: 'text', text: `🗑️ ${files.length} arquivo(s) deletado(s).` }] };
648
+ }
649
+ case 'pulsecloud_file_rename': {
650
+ reqAuth();
651
+ const { server_id, from, to } = args;
652
+ await ptero.renameFile(server_id, from, to);
653
+ return { content: [{ type: 'text', text: `✏️ \`${from}\` → \`${to}\`` }] };
654
+ }
655
+ case 'pulsecloud_file_compress': {
656
+ reqAuth();
657
+ const { server_id, files } = args;
658
+ const result = await ptero.compressFiles(server_id, files);
659
+ return { content: [{ type: 'text', text: `📦 Archive criado: \`${result.name || 'archive.tar.gz'}\`` }] };
660
+ }
661
+ case 'pulsecloud_file_decompress': {
662
+ reqAuth();
663
+ const { server_id, file } = args;
664
+ await ptero.decompressFile(server_id, file);
665
+ return { content: [{ type: 'text', text: `📦 Archive \`${file}\` descomprimido.` }] };
666
+ }
667
+ case 'pulsecloud_file_download_url': {
668
+ reqAuth();
669
+ const { server_id, file } = args;
670
+ const url = ptero.getDownloadUrl(server_id, file);
671
+ return { content: [{ type: 'text', text: `🔗 URL de download:\n${url}` }] };
672
+ }
673
+ case 'pulsecloud_file_create_directory': {
674
+ reqAuth();
675
+ const { server_id, name } = args;
676
+ await ptero.createDirectory(server_id, name);
677
+ return { content: [{ type: 'text', text: `📁 Pasta \`${name}\` criada.` }] };
678
+ }
679
+ // ─── BACKUPS ───────────────────────────────────────────────
680
+ case 'pulsecloud_backup_list': {
681
+ reqAuth();
682
+ const { server_id } = args;
683
+ const backups = await ptero.listBackups(server_id);
684
+ if (backups.length === 0)
685
+ return { content: [{ type: 'text', text: '📭 Nenhum backup.' }] };
686
+ return { content: [{ type: 'text', text: `## 💾 Backups\n\n\`\`\`json\n${JSON.stringify(backups, null, 2)}\n\`\`\`` }] };
687
+ }
688
+ case 'pulsecloud_backup_create': {
689
+ reqAuth();
690
+ const { server_id, name } = args;
691
+ const result = await ptero.createBackup(server_id, name);
692
+ return { content: [{ type: 'text', text: `✅ Backup criado!\n\n${JSON.stringify(result, null, 2)}` }] };
693
+ }
694
+ case 'pulsecloud_backup_download_url': {
695
+ reqAuth();
696
+ const { server_id, backup_id } = args;
697
+ const url = await ptero.getBackupDownloadUrl(server_id, backup_id);
698
+ return { content: [{ type: 'text', text: `🔗 URL de download:\n${url}` }] };
699
+ }
700
+ case 'pulsecloud_backup_delete': {
701
+ reqAuth();
702
+ const { server_id, backup_id } = args;
703
+ await ptero.deleteBackup(server_id, backup_id);
704
+ return { content: [{ type: 'text', text: `🗑️ Backup ${backup_id} deletado.` }] };
705
+ }
706
+ // ─── DATABASES ─────────────────────────────────────────────
707
+ case 'pulsecloud_database_list': {
708
+ reqAuth();
709
+ const { server_id } = args;
710
+ const dbs = await ptero.listDatabases(server_id);
711
+ return { content: [{ type: 'text', text: `## 🗄️ Databases\n\n\`\`\`json\n${JSON.stringify(dbs, null, 2)}\n\`\`\`` }] };
712
+ }
713
+ case 'pulsecloud_database_create': {
714
+ reqAuth();
715
+ const { server_id, database_name } = args;
716
+ const result = await ptero.createDatabase(server_id, database_name);
717
+ return { content: [{ type: 'text', text: `✅ Database criado!\n\n${JSON.stringify(result, null, 2)}` }] };
718
+ }
719
+ case 'pulsecloud_database_delete': {
720
+ reqAuth();
721
+ const { server_id, database_id } = args;
722
+ await ptero.deleteDatabase(server_id, database_id);
723
+ return { content: [{ type: 'text', text: `🗑️ Database ${database_id} deletado.` }] };
724
+ }
725
+ case 'pulsecloud_database_rotate_password': {
726
+ reqAuth();
727
+ const { server_id, database_id } = args;
728
+ const result = await ptero.rotateDatabasePassword(server_id, database_id);
729
+ return { content: [{ type: 'text', text: `🔄 Senha rotacionada!\n\n${JSON.stringify(result, null, 2)}` }] };
730
+ }
731
+ // ─── ALLOCATIONS ───────────────────────────────────────────
732
+ case 'pulsecloud_server_allocations': {
733
+ reqAuth();
734
+ const { server_id } = args;
735
+ const allocs = await ptero.listAllocations(server_id);
736
+ return { content: [{ type: 'text', text: `## 🌐 Alocações\n\n\`\`\`json\n${JSON.stringify(allocs, null, 2)}\n\`\`\`` }] };
737
+ }
738
+ case 'pulsecloud_server_set_primary_allocation': {
739
+ reqAuth();
740
+ const { server_id, allocation_id } = args;
741
+ await ptero.setPrimaryAllocation(server_id, allocation_id);
742
+ return { content: [{ type: 'text', text: `✅ Alocação ${allocation_id} definida como primária.` }] };
743
+ }
744
+ // ─── SUBDOMÍNIOS ───────────────────────────────────────────
745
+ case 'pulsecloud_subdomain_list': {
746
+ const hosts = await npm.listProxyHosts();
747
+ if (!hosts || hosts.length === 0) {
748
+ return { content: [{ type: 'text', text: '📭 Nenhum proxy host.' }] };
749
+ }
750
+ const lines = hosts.map((h) => ` ${h.enabled ? '✅' : '⏸️'} **${h.domain_names?.[0] || 'N/A'}** → ${h.forward_scheme}://${h.forward_host}:${h.forward_port}`).join('\n');
751
+ return { content: [{ type: 'text', text: `## 🌐 Proxy Hosts (${hosts.length})\n\n${lines}` }] };
752
+ }
753
+ case 'pulsecloud_subdomain_create': {
754
+ const { domain, forward_host, forward_port, forward_scheme = 'http', ssl_forced = true, create_dns = true, ssl_email } = args;
755
+ if (create_dns) {
756
+ const sub = domain.split('.')[0];
757
+ await cf.createDnsRecord({ type: 'A', name: sub, content: forward_host, proxied: false });
758
+ }
759
+ const npmHost = await npm.createProxyHost({
760
+ domain_names: [domain],
761
+ forward_scheme: forward_scheme,
762
+ forward_host,
763
+ forward_port,
764
+ ssl_forced,
765
+ enabled: true,
766
+ });
767
+ return {
768
+ content: [{
769
+ type: 'text',
770
+ text: `✅ Subdomínio **${domain}** criado!\n → ${forward_scheme}://${forward_host}:${forward_port}\n 📝 DNS: ${create_dns ? 'Criado na Cloudflare' : 'Manual'}\n 🆔 NPM ID: ${npmHost.id}`,
771
+ }],
772
+ };
773
+ }
774
+ case 'pulsecloud_subdomain_delete': {
775
+ const { proxy_id, delete_dns = false } = args;
776
+ const host = await npm.getProxyHost(proxy_id);
777
+ await npm.deleteProxyHost(proxy_id);
778
+ if (delete_dns && host.domain_names?.length) {
779
+ const domain = host.domain_names[0];
780
+ const records = await cf.listDnsRecords('A');
781
+ const match = records.find((r) => domain.includes(r.name));
782
+ if (match)
783
+ await cf.deleteDnsRecord(match.id);
784
+ }
785
+ return { content: [{ type: 'text', text: `🗑️ Proxy host #${proxy_id} deletado.${delete_dns ? ' DNS removido.' : ''}` }] };
786
+ }
787
+ default:
788
+ throw new Error(`Tool desconhecida: ${name}`);
789
+ }
790
+ }
791
+ catch (err) {
792
+ return {
793
+ content: [{ type: 'text', text: `❌ Erro: ${err.message}` }],
794
+ isError: true,
795
+ };
796
+ }
797
+ });
798
+ function reqAuth() {
799
+ if (!isAuthenticated()) {
800
+ throw new Error('Autenticação necessária. Acesse https://pulsecloud.com.br/mcp/authorize para autorizar.');
801
+ }
802
+ }
803
+ function statusEmoji(status) {
804
+ switch (status) {
805
+ case 'operational': return '✅';
806
+ case 'degraded': return '⚠️';
807
+ case 'partial_outage': return '🔶';
808
+ case 'major_outage': return '🔴';
809
+ default: return '❓';
810
+ }
811
+ }
812
+ function formatBytes(bytes) {
813
+ if (!bytes || bytes <= 0)
814
+ return '0';
815
+ if (bytes >= 1048576)
816
+ return `${(bytes / 1048576).toFixed(1)} GB`;
817
+ if (bytes >= 1024)
818
+ return `${(bytes / 1024).toFixed(0)} MB`;
819
+ return `${bytes} KB`;
820
+ }
821
+ function formatMB(mb) {
822
+ if (!mb || mb <= 0)
823
+ return '0';
824
+ if (mb >= 1024)
825
+ return `${(mb / 1024).toFixed(1)} GB`;
826
+ return `${mb} MB`;
827
+ }
828
+ // ─── REMOVE OLD server.ts REFERENCES ─────────────────────────────
829
+ // The server.ts file is no longer needed. Run via stdio only.
830
+ // If server.ts exists, it should be deleted.
831
+ const transport = new StdioServerTransport();
832
+ await server.connect(transport);
833
+ //# sourceMappingURL=index.js.map