@edilsonfjdev/mcp-setup 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/LICENSE +21 -0
- package/README.md +129 -0
- package/bin/mcp-setup.js +3 -0
- package/dist/add-2HOZTV5H.js +66 -0
- package/dist/chunk-H3PELLAE.js +162 -0
- package/dist/chunk-JNVJQZTK.js +169 -0
- package/dist/chunk-Q36CDQK6.js +28 -0
- package/dist/chunk-UZ72QCYF.js +79 -0
- package/dist/config-loader-O23HRZIK.js +14 -0
- package/dist/doctor-OBFIE3JN.js +161 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +42 -0
- package/dist/install-DAWHBGCE.js +169 -0
- package/dist/list-QO4GBJHR.js +29 -0
- package/dist/profiles-HU3NVRTV.js +30 -0
- package/dist/remove-T6C2DD7O.js +39 -0
- package/dist/update-GOCA4IZG.js +55 -0
- package/dist/validate-6RKOV7JG.js +167 -0
- package/package.json +74 -0
- package/templates/mcps/clickup.json +35 -0
- package/templates/mcps/context7.json +19 -0
- package/templates/mcps/coolify.json +24 -0
- package/templates/mcps/figma.json +19 -0
- package/templates/mcps/github.json +22 -0
- package/templates/mcps/hetzner.json +23 -0
- package/templates/mcps/linear.json +33 -0
- package/templates/mcps/n8n-docs.json +20 -0
- package/templates/mcps/n8n-instance.json +22 -0
- package/templates/mcps/playwright.json +20 -0
- package/templates/mcps/postgres.json +20 -0
- package/templates/mcps/semgrep.json +19 -0
- package/templates/mcps/sentry.json +34 -0
- package/templates/mcps/slack.json +34 -0
- package/templates/mcps/stitch.json +22 -0
- package/templates/profiles/agents.json +7 -0
- package/templates/profiles/base.json +7 -0
- package/templates/profiles/full.json +7 -0
- package/templates/profiles/headless.json +20 -0
- package/templates/profiles/minimal.json +7 -0
- package/templates/profiles/saas.json +7 -0
- package/templates/scopes/scope-map.json +24 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Edilson Júnior
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# MCP Setup
|
|
2
|
+
|
|
3
|
+
> Solução padronizada para instalação e configuração de MCP Servers no Claude Code, adaptável a qualquer tipo de projeto.
|
|
4
|
+
|
|
5
|
+
## Visão Geral
|
|
6
|
+
|
|
7
|
+
O MCP Setup é uma solução que padroniza e automatiza a configuração de servidores MCP (Model Context Protocol) para o Claude Code. Permite que desenvolvedores, arquitetos e equipes configurem rapidamente as integrações necessárias para seus projetos, sem precisar repetir trabalho manual ou conhecer detalhes de autenticação de cada serviço.
|
|
8
|
+
|
|
9
|
+
Destinado a profissionais e equipes que trabalham com múltiplos projetos de software — desde sites estáticos até plataformas SaaS com pagamentos, automação e IA.
|
|
10
|
+
|
|
11
|
+
## Problema
|
|
12
|
+
|
|
13
|
+
Configurar integrações MCP manualmente para cada novo projeto gera:
|
|
14
|
+
|
|
15
|
+
- **Retrabalho** — mesmas configurações repetidas em cada projeto
|
|
16
|
+
- **Inconsistência** — configurações divergem entre projetos e membros da equipe
|
|
17
|
+
- **Risco de segurança** — credenciais mal gerenciadas ou expostas em repositórios
|
|
18
|
+
- **Barreira de entrada** — cada integração exige conhecimento específico de autenticação e configuração
|
|
19
|
+
|
|
20
|
+
## Funcionalidades
|
|
21
|
+
|
|
22
|
+
- Perfis de configuração pré-definidos por tipo de projeto (5 templates)
|
|
23
|
+
- Instalação automatizada e interativa de servidores MCP
|
|
24
|
+
- Gestão de escopos: global (user), projeto (project) e local
|
|
25
|
+
- Separação segura de credenciais via variáveis de ambiente
|
|
26
|
+
- Documentação integrada de credenciais, segurança e troubleshooting
|
|
27
|
+
- Suporte a 14 integrações MCP padronizadas
|
|
28
|
+
|
|
29
|
+
## Templates Disponíveis
|
|
30
|
+
|
|
31
|
+
| Template | MCPs | Cenário de Uso |
|
|
32
|
+
|----------|------|----------------|
|
|
33
|
+
| `minimal` | 3 | Sites estáticos, portfólios, landing pages |
|
|
34
|
+
| `base` | 9 | Projetos padrão com PM, design e deploy |
|
|
35
|
+
| `saas` | 12 | SaaS, CRM, plataformas com pagamentos e banco de dados |
|
|
36
|
+
| `agents` | 14+ | Projetos com agentes IA, workflows N8N, automação |
|
|
37
|
+
| `full` | 14 | Setup global completo (escopo user) |
|
|
38
|
+
|
|
39
|
+
## MCPs Suportados
|
|
40
|
+
|
|
41
|
+
| Categoria | MCP | Autenticação |
|
|
42
|
+
|-----------|-----|-------------|
|
|
43
|
+
| **Desenvolvimento** | GitHub | Bearer Token |
|
|
44
|
+
| **Documentação** | Context7 | Nenhuma |
|
|
45
|
+
| **Design** | Figma | OAuth |
|
|
46
|
+
| | Stitch | API Key |
|
|
47
|
+
| **Gestão e Comunicação** | Linear | OAuth |
|
|
48
|
+
| | Slack | OAuth |
|
|
49
|
+
| | ClickUp | OAuth |
|
|
50
|
+
| **Infraestrutura** | Hetzner | API Token |
|
|
51
|
+
| | Coolify | API Token |
|
|
52
|
+
| **Banco de Dados** | PostgreSQL | DSN |
|
|
53
|
+
| **Workflow** | N8N Docs | Nenhuma |
|
|
54
|
+
| | N8N Instance | Bearer Token |
|
|
55
|
+
| **Segurança e Monitoramento** | Semgrep | Nenhuma |
|
|
56
|
+
| | Sentry | OAuth |
|
|
57
|
+
| **Testes** | Playwright | Nenhuma |
|
|
58
|
+
|
|
59
|
+
## Estrutura do Projeto
|
|
60
|
+
|
|
61
|
+
```
|
|
62
|
+
mcp-setup/
|
|
63
|
+
├── CLAUDE.md
|
|
64
|
+
├── CHANGELOG.md
|
|
65
|
+
├── README.md
|
|
66
|
+
├── LICENSE
|
|
67
|
+
├── .gitignore
|
|
68
|
+
├── .claude/
|
|
69
|
+
│ ├── rules/
|
|
70
|
+
│ └── documents/
|
|
71
|
+
│ ├── tbs/ # Technical Briefs
|
|
72
|
+
│ ├── adrs/ # Architecture Decision Records
|
|
73
|
+
│ ├── specs/ # Especificações
|
|
74
|
+
│ └── irs/ # Implementation Records
|
|
75
|
+
├── repo/
|
|
76
|
+
│ └── mcp-project-ecope/ # Protótipo e escopo original
|
|
77
|
+
├── templates/ # Templates MCP por tipo de projeto
|
|
78
|
+
├── scripts/ # Scripts de instalação e utilitários
|
|
79
|
+
├── docs/ # Documentação do projeto
|
|
80
|
+
│ ├── credentials.md
|
|
81
|
+
│ ├── security.md
|
|
82
|
+
│ ├── scopes.md
|
|
83
|
+
│ └── troubleshooting.md
|
|
84
|
+
└── tests/ # Testes automatizados
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
## Quick Start
|
|
88
|
+
|
|
89
|
+
```bash
|
|
90
|
+
# 1. Clone o repositório
|
|
91
|
+
git clone <repo-url> && cd mcp-setup
|
|
92
|
+
|
|
93
|
+
# 2. Copie o arquivo de variáveis de ambiente
|
|
94
|
+
cp .env.example .env
|
|
95
|
+
|
|
96
|
+
# 3. Preencha as credenciais no .env
|
|
97
|
+
# Consulte docs/credentials.md para instruções detalhadas
|
|
98
|
+
|
|
99
|
+
# 4. Execute a instalação com o template desejado
|
|
100
|
+
./scripts/install.sh --template base
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
> Instruções detalhadas de instalação serão disponibilizadas após a fase de implementação.
|
|
104
|
+
|
|
105
|
+
## Documentação
|
|
106
|
+
|
|
107
|
+
| Documento | Descrição |
|
|
108
|
+
|-----------|-----------|
|
|
109
|
+
| [Credenciais](docs/credentials.md) | Como obter e configurar credenciais para cada MCP |
|
|
110
|
+
| [Segurança](docs/security.md) | Boas práticas de segurança para configuração MCP |
|
|
111
|
+
| [Escopos](docs/scopes.md) | Gestão de escopos: user, project e local |
|
|
112
|
+
| [Troubleshooting](docs/troubleshooting.md) | Resolução de problemas comuns |
|
|
113
|
+
|
|
114
|
+
## Roadmap
|
|
115
|
+
|
|
116
|
+
- [ ] CLI próprio para instalação e gestão de MCPs
|
|
117
|
+
- [ ] Suporte nativo a Windows (PowerShell/CMD)
|
|
118
|
+
- [ ] Validação automática de credenciais durante instalação
|
|
119
|
+
- [ ] Composição dinâmica de templates (merge/overlay)
|
|
120
|
+
- [ ] Mecanismo de atualização de MCPs já instalados
|
|
121
|
+
- [ ] Testes automatizados para scripts e templates
|
|
122
|
+
|
|
123
|
+
## Licença
|
|
124
|
+
|
|
125
|
+
MIT
|
|
126
|
+
|
|
127
|
+
## Autor
|
|
128
|
+
|
|
129
|
+
**Edilson Jr** — Tech Advisor & Arquiteto e Engenheiro de Software
|
package/bin/mcp-setup.js
ADDED
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loadEnv,
|
|
3
|
+
validateCredential
|
|
4
|
+
} from "./chunk-JNVJQZTK.js";
|
|
5
|
+
import {
|
|
6
|
+
executeCommand,
|
|
7
|
+
generateCommand,
|
|
8
|
+
resolveScope
|
|
9
|
+
} from "./chunk-H3PELLAE.js";
|
|
10
|
+
import {
|
|
11
|
+
listMcps,
|
|
12
|
+
loadMcp
|
|
13
|
+
} from "./chunk-UZ72QCYF.js";
|
|
14
|
+
import {
|
|
15
|
+
logger
|
|
16
|
+
} from "./chunk-Q36CDQK6.js";
|
|
17
|
+
|
|
18
|
+
// src/commands/add.ts
|
|
19
|
+
async function addCommand(mcp, options) {
|
|
20
|
+
const { scope, dryRun } = options;
|
|
21
|
+
let mcpDef;
|
|
22
|
+
try {
|
|
23
|
+
mcpDef = loadMcp(mcp);
|
|
24
|
+
} catch {
|
|
25
|
+
const available = listMcps().map((m) => m.name).join(", ");
|
|
26
|
+
logger.error(`MCP "${mcp}" n\xE3o encontrado. Dispon\xEDveis: ${available}`);
|
|
27
|
+
process.exit(1);
|
|
28
|
+
}
|
|
29
|
+
logger.info(`Adicionando MCP "${mcpDef.displayName}"...`);
|
|
30
|
+
if (mcpDef.auth.envVar) {
|
|
31
|
+
const envResult = loadEnv();
|
|
32
|
+
if (envResult.success) {
|
|
33
|
+
const value = envResult.vars[mcpDef.auth.envVar];
|
|
34
|
+
if (!value) {
|
|
35
|
+
logger.warn(`Vari\xE1vel ${mcpDef.auth.envVar} n\xE3o encontrada no .env`);
|
|
36
|
+
} else {
|
|
37
|
+
const validation = validateCredential(mcpDef.auth.envVar, value);
|
|
38
|
+
if (!validation.valid) {
|
|
39
|
+
logger.warn(`${mcpDef.auth.envVar}: ${validation.error}`);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const resolvedScope = resolveScope(mcp, scope);
|
|
45
|
+
const cmd = generateCommand(mcpDef, resolvedScope);
|
|
46
|
+
if (dryRun) {
|
|
47
|
+
logger.info(`[dry-run] ${cmd.command}`);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
const spinner = logger.spinner(`Instalando ${mcpDef.displayName}...`);
|
|
51
|
+
const result = await executeCommand(cmd, false);
|
|
52
|
+
spinner.stop();
|
|
53
|
+
if (result.success) {
|
|
54
|
+
if (result.skipped) {
|
|
55
|
+
logger.warn(`MCP ${mcpDef.displayName} j\xE1 est\xE1 configurado`);
|
|
56
|
+
} else {
|
|
57
|
+
logger.success(`MCP ${mcpDef.displayName} adicionado com sucesso (escopo: ${resolvedScope})`);
|
|
58
|
+
}
|
|
59
|
+
} else {
|
|
60
|
+
logger.error(`Falha ao adicionar ${mcpDef.displayName}: ${result.message}`);
|
|
61
|
+
process.exit(1);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
export {
|
|
65
|
+
addCommand
|
|
66
|
+
};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import {
|
|
2
|
+
loadScopeMap
|
|
3
|
+
} from "./chunk-UZ72QCYF.js";
|
|
4
|
+
|
|
5
|
+
// src/core/scope-manager.ts
|
|
6
|
+
function resolveScope(mcpName, overrideScope) {
|
|
7
|
+
if (overrideScope) {
|
|
8
|
+
const scopeMap2 = loadScopeMap();
|
|
9
|
+
if (scopeMap2.project.includes(mcpName) && overrideScope === "user") {
|
|
10
|
+
return "project";
|
|
11
|
+
}
|
|
12
|
+
return overrideScope;
|
|
13
|
+
}
|
|
14
|
+
const scopeMap = loadScopeMap();
|
|
15
|
+
if (scopeMap.user.includes(mcpName)) return "user";
|
|
16
|
+
if (scopeMap.project.includes(mcpName)) return "project";
|
|
17
|
+
return "user";
|
|
18
|
+
}
|
|
19
|
+
function resolveScopesForProfile(mcpNames, overrideScope) {
|
|
20
|
+
const scopes = /* @__PURE__ */ new Map();
|
|
21
|
+
for (const name of mcpNames) {
|
|
22
|
+
scopes.set(name, resolveScope(name, overrideScope));
|
|
23
|
+
}
|
|
24
|
+
return scopes;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// src/core/mcp-installer.ts
|
|
28
|
+
import { exec as execCb } from "child_process";
|
|
29
|
+
import { promisify } from "util";
|
|
30
|
+
var exec = promisify(execCb);
|
|
31
|
+
async function getInstalledMcps() {
|
|
32
|
+
try {
|
|
33
|
+
const { stdout } = await exec("claude mcp list");
|
|
34
|
+
return stdout.split("\n").map((line) => line.trim().split(/\s+/)[0]).filter((name) => name && name.length > 0 && !name.startsWith("-") && !name.startsWith("="));
|
|
35
|
+
} catch {
|
|
36
|
+
return [];
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
function filterAlreadyInstalled(mcps, installed) {
|
|
40
|
+
const installedSet = new Set(installed.map((n) => n.toLowerCase()));
|
|
41
|
+
const pending = [];
|
|
42
|
+
const existing = [];
|
|
43
|
+
for (const mcp of mcps) {
|
|
44
|
+
if (installedSet.has(mcp.name.toLowerCase())) {
|
|
45
|
+
existing.push(mcp);
|
|
46
|
+
} else {
|
|
47
|
+
pending.push(mcp);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return { pending, existing };
|
|
51
|
+
}
|
|
52
|
+
function applyHeadlessMode(mcps) {
|
|
53
|
+
const resolved = [];
|
|
54
|
+
const skipped = [];
|
|
55
|
+
for (const mcp of mcps) {
|
|
56
|
+
if (mcp.auth.type === "oauth") {
|
|
57
|
+
const raw = mcp;
|
|
58
|
+
if (raw.headlessConfig && raw.auth?.headless) {
|
|
59
|
+
resolved.push({
|
|
60
|
+
...mcp,
|
|
61
|
+
config: raw.headlessConfig,
|
|
62
|
+
auth: {
|
|
63
|
+
type: raw.auth.headless.type,
|
|
64
|
+
envVar: raw.auth.headless.envVar,
|
|
65
|
+
required: raw.auth.headless.required ?? true
|
|
66
|
+
}
|
|
67
|
+
});
|
|
68
|
+
} else {
|
|
69
|
+
skipped.push(mcp);
|
|
70
|
+
}
|
|
71
|
+
} else {
|
|
72
|
+
resolved.push(mcp);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return { resolved, skipped };
|
|
76
|
+
}
|
|
77
|
+
function generateCommand(mcp, scope) {
|
|
78
|
+
const parts = ["claude", "mcp", "add", mcp.name, "-s", scope];
|
|
79
|
+
if (mcp.config.type === "http") {
|
|
80
|
+
parts.push("--url", mcp.config.url);
|
|
81
|
+
if (mcp.config.headers) {
|
|
82
|
+
for (const [key, value] of Object.entries(mcp.config.headers)) {
|
|
83
|
+
parts.push("-H", `"${key}: ${value}"`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
} else if (mcp.config.type === "stdio") {
|
|
87
|
+
parts.push("--", mcp.config.command);
|
|
88
|
+
if (mcp.config.args) {
|
|
89
|
+
parts.push(...mcp.config.args);
|
|
90
|
+
}
|
|
91
|
+
if (mcp.config.env) {
|
|
92
|
+
for (const [key, value] of Object.entries(mcp.config.env)) {
|
|
93
|
+
parts.push("-e", `${key}=${value}`);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
return {
|
|
98
|
+
mcpName: mcp.name,
|
|
99
|
+
command: parts.join(" "),
|
|
100
|
+
scope
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
function generateCommands(mcps, scopes) {
|
|
104
|
+
return mcps.map((mcp) => {
|
|
105
|
+
const scope = scopes.get(mcp.name) || "user";
|
|
106
|
+
return generateCommand(mcp, scope);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
async function executeCommand(cmd, dryRun) {
|
|
110
|
+
if (dryRun) {
|
|
111
|
+
return {
|
|
112
|
+
mcpName: cmd.mcpName,
|
|
113
|
+
success: true,
|
|
114
|
+
message: `[dry-run] ${cmd.command}`,
|
|
115
|
+
skipped: false
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
try {
|
|
119
|
+
const { stdout } = await exec(cmd.command);
|
|
120
|
+
return {
|
|
121
|
+
mcpName: cmd.mcpName,
|
|
122
|
+
success: true,
|
|
123
|
+
message: stdout.trim() || `MCP ${cmd.mcpName} instalado com sucesso`,
|
|
124
|
+
skipped: false
|
|
125
|
+
};
|
|
126
|
+
} catch (error) {
|
|
127
|
+
if (error.stderr?.includes("already exists") || error.stderr?.includes("j\xE1 existe")) {
|
|
128
|
+
return {
|
|
129
|
+
mcpName: cmd.mcpName,
|
|
130
|
+
success: true,
|
|
131
|
+
message: `MCP ${cmd.mcpName} j\xE1 est\xE1 configurado (ignorado)`,
|
|
132
|
+
skipped: true
|
|
133
|
+
};
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
mcpName: cmd.mcpName,
|
|
137
|
+
success: false,
|
|
138
|
+
message: error.stderr || error.message,
|
|
139
|
+
skipped: false
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async function executeCommands(commands, dryRun) {
|
|
144
|
+
const results = [];
|
|
145
|
+
for (const cmd of commands) {
|
|
146
|
+
const result = await executeCommand(cmd, dryRun);
|
|
147
|
+
results.push(result);
|
|
148
|
+
}
|
|
149
|
+
return results;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export {
|
|
153
|
+
resolveScope,
|
|
154
|
+
resolveScopesForProfile,
|
|
155
|
+
getInstalledMcps,
|
|
156
|
+
filterAlreadyInstalled,
|
|
157
|
+
applyHeadlessMode,
|
|
158
|
+
generateCommand,
|
|
159
|
+
generateCommands,
|
|
160
|
+
executeCommand,
|
|
161
|
+
executeCommands
|
|
162
|
+
};
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
// src/validation/schemas.ts
|
|
2
|
+
import { z } from "zod";
|
|
3
|
+
var githubTokenSchema = z.string({
|
|
4
|
+
required_error: "GITHUB_TOKEN \xE9 obrigat\xF3rio"
|
|
5
|
+
}).regex(
|
|
6
|
+
/^(ghp_[a-zA-Z0-9]{36}|github_pat_[a-zA-Z0-9_]{82,})$/,
|
|
7
|
+
{ message: "GITHUB_TOKEN deve come\xE7ar com ghp_ (classic) ou github_pat_ (fine-grained)" }
|
|
8
|
+
);
|
|
9
|
+
var hetznerTokenSchema = z.string({
|
|
10
|
+
required_error: "HETZNER_API_TOKEN \xE9 obrigat\xF3rio"
|
|
11
|
+
}).regex(
|
|
12
|
+
/^[a-zA-Z0-9]{64}$/,
|
|
13
|
+
{ message: "HETZNER_API_TOKEN deve ter exatamente 64 caracteres alfanum\xE9ricos" }
|
|
14
|
+
);
|
|
15
|
+
var coolifyUrlSchema = z.string({
|
|
16
|
+
required_error: "COOLIFY_BASE_URL \xE9 obrigat\xF3rio"
|
|
17
|
+
}).regex(
|
|
18
|
+
/^https?:\/\/.+/,
|
|
19
|
+
{ message: "COOLIFY_BASE_URL deve ser uma URL v\xE1lida (http:// ou https://)" }
|
|
20
|
+
);
|
|
21
|
+
var coolifyTokenSchema = z.string({
|
|
22
|
+
required_error: "COOLIFY_ACCESS_TOKEN \xE9 obrigat\xF3rio"
|
|
23
|
+
}).min(40, { message: "COOLIFY_ACCESS_TOKEN deve ter pelo menos 40 caracteres" });
|
|
24
|
+
var databaseUrlSchema = z.string({
|
|
25
|
+
required_error: "DATABASE_URL \xE9 obrigat\xF3rio"
|
|
26
|
+
}).regex(
|
|
27
|
+
/^postgres(ql)?:\/\/.+/,
|
|
28
|
+
{ message: "DATABASE_URL deve come\xE7ar com postgres:// ou postgresql://" }
|
|
29
|
+
);
|
|
30
|
+
var stripeKeySchema = z.string({
|
|
31
|
+
required_error: "STRIPE_SECRET_KEY \xE9 obrigat\xF3rio"
|
|
32
|
+
}).refine(
|
|
33
|
+
(val) => !val.startsWith("sk_live_"),
|
|
34
|
+
{ message: "PROIBIDO: Chave Stripe de produ\xE7\xE3o (sk_live_) detectada. Use apenas sk_test_ para seguran\xE7a." }
|
|
35
|
+
).pipe(
|
|
36
|
+
z.string().regex(
|
|
37
|
+
/^sk_test_[a-zA-Z0-9]{24,}$/,
|
|
38
|
+
{ message: "STRIPE_SECRET_KEY deve come\xE7ar com sk_test_ seguido de no m\xEDnimo 24 caracteres" }
|
|
39
|
+
)
|
|
40
|
+
);
|
|
41
|
+
var n8nUrlSchema = z.string({
|
|
42
|
+
required_error: "N8N_URL \xE9 obrigat\xF3rio"
|
|
43
|
+
}).regex(
|
|
44
|
+
/^https?:\/\/.+/,
|
|
45
|
+
{ message: "N8N_URL deve ser uma URL v\xE1lida (http:// ou https://)" }
|
|
46
|
+
);
|
|
47
|
+
var n8nTokenSchema = z.string({
|
|
48
|
+
required_error: "N8N_MCP_TOKEN \xE9 obrigat\xF3rio"
|
|
49
|
+
}).min(1, { message: "N8N_MCP_TOKEN n\xE3o pode ser vazio" });
|
|
50
|
+
var stitchKeySchema = z.string({
|
|
51
|
+
required_error: "STITCH_API_KEY \xE9 obrigat\xF3rio"
|
|
52
|
+
}).min(1, { message: "STITCH_API_KEY n\xE3o pode ser vazio" });
|
|
53
|
+
var credentialSchemas = {
|
|
54
|
+
GITHUB_TOKEN: githubTokenSchema,
|
|
55
|
+
HETZNER_API_TOKEN: hetznerTokenSchema,
|
|
56
|
+
COOLIFY_BASE_URL: coolifyUrlSchema,
|
|
57
|
+
COOLIFY_ACCESS_TOKEN: coolifyTokenSchema,
|
|
58
|
+
DATABASE_URL: databaseUrlSchema,
|
|
59
|
+
STRIPE_SECRET_KEY: stripeKeySchema,
|
|
60
|
+
N8N_URL: n8nUrlSchema,
|
|
61
|
+
N8N_MCP_TOKEN: n8nTokenSchema,
|
|
62
|
+
STITCH_API_KEY: stitchKeySchema
|
|
63
|
+
};
|
|
64
|
+
var requiredVarsByTemplate = {
|
|
65
|
+
minimal: ["GITHUB_TOKEN"],
|
|
66
|
+
base: ["GITHUB_TOKEN", "STITCH_API_KEY", "COOLIFY_BASE_URL", "COOLIFY_ACCESS_TOKEN"],
|
|
67
|
+
saas: ["GITHUB_TOKEN", "STITCH_API_KEY", "COOLIFY_BASE_URL", "COOLIFY_ACCESS_TOKEN", "DATABASE_URL", "STRIPE_SECRET_KEY"],
|
|
68
|
+
agents: ["GITHUB_TOKEN", "STITCH_API_KEY", "COOLIFY_BASE_URL", "COOLIFY_ACCESS_TOKEN", "DATABASE_URL", "HETZNER_API_TOKEN", "N8N_URL", "N8N_MCP_TOKEN"],
|
|
69
|
+
full: ["GITHUB_TOKEN", "STITCH_API_KEY", "COOLIFY_BASE_URL", "COOLIFY_ACCESS_TOKEN", "HETZNER_API_TOKEN"]
|
|
70
|
+
};
|
|
71
|
+
function validateCredential(name, value) {
|
|
72
|
+
const schema = credentialSchemas[name];
|
|
73
|
+
if (!schema) {
|
|
74
|
+
return { valid: true, variable: name };
|
|
75
|
+
}
|
|
76
|
+
const result = schema.safeParse(value);
|
|
77
|
+
return {
|
|
78
|
+
valid: result.success,
|
|
79
|
+
variable: name,
|
|
80
|
+
error: result.success ? void 0 : result.error.errors[0]?.message
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
function validateAllCredentials(templateName, env) {
|
|
84
|
+
const required = requiredVarsByTemplate[templateName] || [];
|
|
85
|
+
return required.map((varName) => {
|
|
86
|
+
const value = env[varName];
|
|
87
|
+
if (!value) {
|
|
88
|
+
return {
|
|
89
|
+
valid: false,
|
|
90
|
+
variable: varName,
|
|
91
|
+
error: `${varName} \xE9 obrigat\xF3rio para o template "${templateName}"`
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
return validateCredential(varName, value);
|
|
95
|
+
});
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// src/utils/env.ts
|
|
99
|
+
import { config as dotenvConfig } from "dotenv";
|
|
100
|
+
import { existsSync } from "fs";
|
|
101
|
+
import { resolve } from "path";
|
|
102
|
+
function loadEnv(envPath) {
|
|
103
|
+
const path = envPath || resolve(process.cwd(), ".env");
|
|
104
|
+
if (!existsSync(path)) {
|
|
105
|
+
return {
|
|
106
|
+
success: false,
|
|
107
|
+
vars: {},
|
|
108
|
+
path,
|
|
109
|
+
error: `Arquivo .env n\xE3o encontrado em ${path}. Copie .env.example para .env e preencha as credenciais.`
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
const result = dotenvConfig({ path });
|
|
113
|
+
if (result.error) {
|
|
114
|
+
return {
|
|
115
|
+
success: false,
|
|
116
|
+
vars: {},
|
|
117
|
+
path,
|
|
118
|
+
error: `Erro ao carregar .env: ${result.error.message}`
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
return {
|
|
122
|
+
success: true,
|
|
123
|
+
vars: result.parsed || {},
|
|
124
|
+
path
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
var varDescriptions = {
|
|
128
|
+
GITHUB_TOKEN: { description: "Token de acesso pessoal do GitHub (PAT)", mcpName: "github" },
|
|
129
|
+
STITCH_API_KEY: { description: "Chave de API do Google Stitch", mcpName: "stitch" },
|
|
130
|
+
HETZNER_API_TOKEN: { description: "Token de API do Hetzner Cloud", mcpName: "hetzner" },
|
|
131
|
+
COOLIFY_BASE_URL: { description: "URL base da inst\xE2ncia Coolify", mcpName: "coolify" },
|
|
132
|
+
COOLIFY_ACCESS_TOKEN: { description: "Token de acesso da API Coolify", mcpName: "coolify" },
|
|
133
|
+
DATABASE_URL: { description: "String de conex\xE3o PostgreSQL (DSN)", mcpName: "postgres" },
|
|
134
|
+
STRIPE_SECRET_KEY: { description: "Chave secreta do Stripe (apenas sk_test_)", mcpName: "stripe" },
|
|
135
|
+
N8N_URL: { description: "URL da inst\xE2ncia N8N", mcpName: "n8n-instance" },
|
|
136
|
+
N8N_MCP_TOKEN: { description: "Token MCP da inst\xE2ncia N8N", mcpName: "n8n-instance" }
|
|
137
|
+
};
|
|
138
|
+
function getRequiredVars(templateName) {
|
|
139
|
+
const varNames = requiredVarsByTemplate[templateName] || [];
|
|
140
|
+
return varNames.map((name) => ({
|
|
141
|
+
name,
|
|
142
|
+
description: varDescriptions[name]?.description || name,
|
|
143
|
+
mcpName: varDescriptions[name]?.mcpName || "desconhecido"
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
146
|
+
function validatePresence(env, required) {
|
|
147
|
+
const present = [];
|
|
148
|
+
const missing = [];
|
|
149
|
+
for (const req of required) {
|
|
150
|
+
if (env[req.name] && env[req.name].trim() !== "") {
|
|
151
|
+
present.push(req.name);
|
|
152
|
+
} else {
|
|
153
|
+
missing.push(req);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
return {
|
|
157
|
+
valid: missing.length === 0,
|
|
158
|
+
present,
|
|
159
|
+
missing
|
|
160
|
+
};
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export {
|
|
164
|
+
validateCredential,
|
|
165
|
+
validateAllCredentials,
|
|
166
|
+
loadEnv,
|
|
167
|
+
getRequiredVars,
|
|
168
|
+
validatePresence
|
|
169
|
+
};
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// src/utils/logger.ts
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import ora from "ora";
|
|
4
|
+
var noColor = process.argv.includes("--no-color");
|
|
5
|
+
var verbose = process.argv.includes("--verbose");
|
|
6
|
+
if (noColor) {
|
|
7
|
+
chalk.level = 0;
|
|
8
|
+
}
|
|
9
|
+
var logger = {
|
|
10
|
+
success: (msg) => console.log(chalk.green("\u2713"), msg),
|
|
11
|
+
error: (msg) => console.error(chalk.red("\u2717"), msg),
|
|
12
|
+
warn: (msg) => console.log(chalk.yellow("!"), msg),
|
|
13
|
+
info: (msg) => console.log(chalk.blue("\u2139"), msg),
|
|
14
|
+
debug: (msg) => {
|
|
15
|
+
if (verbose) console.log(chalk.gray("\u22EF"), msg);
|
|
16
|
+
},
|
|
17
|
+
spinner: (msg) => {
|
|
18
|
+
if (noColor) {
|
|
19
|
+
console.log(`... ${msg}`);
|
|
20
|
+
return ora({ text: msg, isEnabled: false });
|
|
21
|
+
}
|
|
22
|
+
return ora(msg).start();
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export {
|
|
27
|
+
logger
|
|
28
|
+
};
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
// src/core/config-loader.ts
|
|
2
|
+
import { readFileSync, existsSync, readdirSync } from "fs";
|
|
3
|
+
import { resolve, dirname } from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
function templatesDir() {
|
|
6
|
+
const fromCwd = resolve(process.cwd(), "templates");
|
|
7
|
+
if (existsSync(fromCwd)) return fromCwd;
|
|
8
|
+
const __filename2 = fileURLToPath(import.meta.url);
|
|
9
|
+
const __dirname2 = dirname(__filename2);
|
|
10
|
+
return resolve(__dirname2, "..", "templates");
|
|
11
|
+
}
|
|
12
|
+
function loadMcp(name) {
|
|
13
|
+
const filePath = resolve(templatesDir(), "mcps", `${name}.json`);
|
|
14
|
+
if (!existsSync(filePath)) {
|
|
15
|
+
throw new Error(`MCP "${name}" n\xE3o encontrado em templates/mcps/${name}.json`);
|
|
16
|
+
}
|
|
17
|
+
const content = readFileSync(filePath, "utf-8");
|
|
18
|
+
return JSON.parse(content);
|
|
19
|
+
}
|
|
20
|
+
function loadProfile(name) {
|
|
21
|
+
const customPaths = [
|
|
22
|
+
resolve(process.cwd(), ".mcp-setup", "profiles", `${name}.json`),
|
|
23
|
+
resolve(process.env.HOME || process.env.USERPROFILE || "", ".mcp-setup", "profiles", `${name}.json`)
|
|
24
|
+
];
|
|
25
|
+
for (const customPath of customPaths) {
|
|
26
|
+
if (existsSync(customPath)) {
|
|
27
|
+
const content2 = readFileSync(customPath, "utf-8");
|
|
28
|
+
return JSON.parse(content2);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
const filePath = resolve(templatesDir(), "profiles", `${name}.json`);
|
|
32
|
+
if (!existsSync(filePath)) {
|
|
33
|
+
const available = listProfiles().map((p) => p.name).join(", ");
|
|
34
|
+
throw new Error(`Profile "${name}" n\xE3o encontrado. Dispon\xEDveis: ${available}`);
|
|
35
|
+
}
|
|
36
|
+
const content = readFileSync(filePath, "utf-8");
|
|
37
|
+
return JSON.parse(content);
|
|
38
|
+
}
|
|
39
|
+
function loadScopeMap() {
|
|
40
|
+
const filePath = resolve(templatesDir(), "scopes", "scope-map.json");
|
|
41
|
+
const content = readFileSync(filePath, "utf-8");
|
|
42
|
+
return JSON.parse(content);
|
|
43
|
+
}
|
|
44
|
+
function listProfiles() {
|
|
45
|
+
const profilesDir = resolve(templatesDir(), "profiles");
|
|
46
|
+
const files = readdirSync(profilesDir).filter((f) => f.endsWith(".json"));
|
|
47
|
+
return files.map((f) => {
|
|
48
|
+
const content = readFileSync(resolve(profilesDir, f), "utf-8");
|
|
49
|
+
const profile = JSON.parse(content);
|
|
50
|
+
return {
|
|
51
|
+
name: profile.name,
|
|
52
|
+
displayName: profile.displayName,
|
|
53
|
+
description: profile.description,
|
|
54
|
+
mcpCount: profile.mcps.length
|
|
55
|
+
};
|
|
56
|
+
});
|
|
57
|
+
}
|
|
58
|
+
function listMcps() {
|
|
59
|
+
const mcpsDir = resolve(templatesDir(), "mcps");
|
|
60
|
+
const files = readdirSync(mcpsDir).filter((f) => f.endsWith(".json"));
|
|
61
|
+
return files.map((f) => {
|
|
62
|
+
const content = readFileSync(resolve(mcpsDir, f), "utf-8");
|
|
63
|
+
const mcp = JSON.parse(content);
|
|
64
|
+
return {
|
|
65
|
+
name: mcp.name,
|
|
66
|
+
displayName: mcp.displayName,
|
|
67
|
+
category: mcp.category,
|
|
68
|
+
authType: mcp.auth.type
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
export {
|
|
74
|
+
loadMcp,
|
|
75
|
+
loadProfile,
|
|
76
|
+
loadScopeMap,
|
|
77
|
+
listProfiles,
|
|
78
|
+
listMcps
|
|
79
|
+
};
|