@andrebuzeli/git-mcp 15.2.3 → 15.5.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 CHANGED
@@ -2,7 +2,52 @@
2
2
 
3
3
  Servidor MCP (Model Context Protocol) para operações Git locais sem git instalado, com sincronização paralela para GitHub e Gitea.
4
4
 
5
- ## Configuração MCP
5
+ ## Melhorias Implementadas (v15.3.0)
6
+
7
+ ### 🔴 Correções Críticas
8
+ - ✅ **Métodos Faltantes**: `getMergeStatus`, `abortMerge`, `resetHardClean` implementados
9
+ - ✅ **Paralelização runBoth**: GitHub e Gitea executados em paralelo (2x mais rápido)
10
+ - ✅ **withRetry Integrado**: Operações remotas com retry automático
11
+
12
+ ### 🔒 Segurança
13
+ - ✅ **Validação de Path**: Prevenção de path traversal e validação rigorosa
14
+ - ✅ **Logging Configurável**: Debug logging controlado por `DEBUG_AGENT_LOG`
15
+ - ✅ **Limite de Comprimento**: Paths limitados a 260 caracteres (MAX_PATH)
16
+
17
+ ### ⚡ Performance
18
+ - ✅ **Timeout Configurável**: `GIT_TIMEOUT_MS` (default: 60s) para operações Git
19
+ - ✅ **Cache de URLs**: URLs de remotes cacheadas por 10 minutos
20
+ - ✅ **Paginação listAllRepos**: Suporta contas com milhares de repositórios
21
+ - ✅ **Rate Limit Handling**: Backoff automático com Retry-After header
22
+
23
+ ### 🧪 Testabilidade & Desenvolvimento
24
+ - ✅ **Dry Run Mode**: Todas as operações suportam simulação
25
+ - ✅ **Testes Unitários**: Suite Jest com 13 testes passando
26
+ - ✅ **Suporte .env File**: Carrega variáveis de `.env` automaticamente
27
+ - ✅ **Métricas/Telemetria**: Opt-in via `ENABLE_METRICS=true`
28
+
29
+ ### 🔧 Novas Funcionalidades
30
+ - ✅ **Git LFS**: Suporte completo (track, pull, push, status)
31
+ - ✅ **Sistema de Hooks**: Pre/post hooks customizáveis
32
+ - ✅ **Consistência Naming**: camelCase em todas as respostas
33
+
34
+ ### 📊 Variáveis de Ambiente
35
+
36
+ | Variável | Descrição | Default |
37
+ |----------|-----------|---------|
38
+ | `GITHUB_TOKEN` | Token de autenticação GitHub | - |
39
+ | `GITEA_URL` | URL do servidor Gitea | - |
40
+ | `GITEA_TOKEN` | Token de autenticação Gitea | - |
41
+ | `GIT_TIMEOUT_MS` | Timeout para operações Git | 60000 |
42
+ | `DEBUG_AGENT_LOG` | Habilita logging de debug | false |
43
+ | `ENABLE_METRICS` | Habilita métricas | false |
44
+
45
+ ## Status dos Testes
46
+
47
+ ```
48
+ Test Suites: 2 passed, 2 total
49
+ Tests: 13 passed, 13 total
50
+ ```
6
51
 
7
52
  ```json
8
53
  {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andrebuzeli/git-mcp",
3
- "version": "15.2.3",
3
+ "version": "15.5.0",
4
4
  "private": false,
5
5
  "description": "MCP server para Git com operações locais e sincronização paralela GitHub/Gitea",
6
6
  "license": "MIT",
@@ -18,11 +18,26 @@
18
18
  "git-mcp": "src/index.js",
19
19
  "git-mcpv2": "src/index.js"
20
20
  },
21
- "scripts": {},
21
+ "scripts": {
22
+ "test": "node --experimental-vm-modules node_modules/jest/bin/jest.js",
23
+ "test:watch": "node --experimental-vm-modules node_modules/jest/bin/jest.js --watch",
24
+ "test:comprehensive": "node --experimental-vm-modules node_modules/jest/bin/jest.js --config jest.comprehensive.config.js",
25
+ "test:coverage": "node --experimental-vm-modules node_modules/jest/bin/jest.js --config jest.comprehensive.config.js --coverage",
26
+ "test:utils": "node --experimental-vm-modules node_modules/jest/bin/jest.js --config jest.comprehensive.config.js test/comprehensive/test-utils.js",
27
+ "test:gitadapter": "node --experimental-vm-modules node_modules/jest/bin/jest.js --config jest.comprehensive.config.js test/comprehensive/test-gitadapter.js",
28
+ "test:providers": "node --experimental-vm-modules node_modules/jest/bin/jest.js --config jest.comprehensive.config.js test/comprehensive/test-provider-manager.js",
29
+ "test:tools": "node --experimental-vm-modules node_modules/jest/bin/jest.js --config jest.comprehensive.config.js test/comprehensive/test-tools.js",
30
+ "test:resources": "node --experimental-vm-modules node_modules/jest/bin/jest.js --config jest.comprehensive.config.js test/comprehensive/test-resources.js",
31
+ "test:integration": "node --experimental-vm-modules node_modules/jest/bin/jest.js --config jest.comprehensive.config.js test/comprehensive/test-integration.js",
32
+ "test:security": "node --experimental-vm-modules node_modules/jest/bin/jest.js --config jest.comprehensive.config.js test/comprehensive/test-security.js"
33
+ },
22
34
  "dependencies": {
23
35
  "@modelcontextprotocol/sdk": "^0.4.0",
24
36
  "@octokit/rest": "^20.0.0",
25
37
  "ajv": "^8.12.0",
26
38
  "axios": "^1.7.7"
39
+ },
40
+ "devDependencies": {
41
+ "jest": "^30.2.0"
27
42
  }
28
43
  }
package/src/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  import { Server } from "@modelcontextprotocol/sdk/server/index.js";
3
3
  import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
4
4
  import Ajv from "ajv";
5
+ import { loadEnv } from "./utils/env.js";
5
6
  import { asToolError } from "./utils/errors.js";
6
7
  import { ProviderManager } from "./providers/providerManager.js";
7
8
  import { GitAdapter } from "./utils/gitAdapter.js";
@@ -21,8 +22,12 @@ import { createGitPullsTool } from "./tools/git-pulls.js";
21
22
  import { createGitMergeTool } from "./tools/git-merge.js";
22
23
  import { createGitDiffTool } from "./tools/git-diff.js";
23
24
  import { createGitCloneTool } from "./tools/git-clone.js";
25
+ import { createGitHelpTool } from "./tools/git-help.js";
24
26
  import { getResources, readResource } from "./resources/index.js";
25
27
 
28
+ // Carrega variáveis de ambiente do arquivo .env (se existir)
29
+ loadEnv();
30
+
26
31
  const pm = new ProviderManager();
27
32
  const git = new GitAdapter(pm);
28
33
 
@@ -41,7 +46,7 @@ if (!hasGitHub && !hasGitea) {
41
46
 
42
47
  const transport = new StdioServerTransport();
43
48
  const server = new Server(
44
- { name: "git-mcpv2", version: "15.2.1" },
49
+ { name: "git-mcpv2", version: "15.4.0" },
45
50
  { capabilities: { tools: {}, resources: {} } }
46
51
  );
47
52
  server.connect(transport);
@@ -50,6 +55,7 @@ server.connect(transport);
50
55
  const ajv = new Ajv({ allErrors: true });
51
56
 
52
57
  const tools = [
58
+ createGitHelpTool(), // Primeiro: AI pode pedir ajuda
53
59
  createGitWorkflowTool(pm, git),
54
60
  createGitRemoteTool(pm, git),
55
61
  createGitBranchesTool(git),
@@ -12,6 +12,8 @@ export class ProviderManager {
12
12
  this._githubOwner = "";
13
13
  this._giteaOwner = "";
14
14
  this._ownerFetchedAt = 0;
15
+ this._remoteUrlsCache = new Map(); // Cache para URLs de remotes
16
+ this._cacheExpiry = 10 * 60 * 1000; // 10 minutos
15
17
  }
16
18
 
17
19
  async getGitHubOwner() {
@@ -47,8 +49,19 @@ export class ProviderManager {
47
49
  }
48
50
 
49
51
  async getRemoteUrls(repoName) {
52
+ const cacheKey = `urls_${repoName}`;
53
+ const now = Date.now();
54
+
55
+ // Verificar cache
56
+ if (this._remoteUrlsCache.has(cacheKey)) {
57
+ const cached = this._remoteUrlsCache.get(cacheKey);
58
+ if (now - cached.timestamp < this._cacheExpiry) {
59
+ return cached.urls;
60
+ }
61
+ }
62
+
50
63
  const urls = {};
51
-
64
+
52
65
  // GitHub URL
53
66
  if (this.github) {
54
67
  const owner = await this.getGitHubOwner();
@@ -66,10 +79,15 @@ export class ProviderManager {
66
79
  }
67
80
  }
68
81
 
82
+ // Cachear resultado
83
+ this._remoteUrlsCache.set(cacheKey, { urls, timestamp: now });
84
+
69
85
  return urls;
70
86
  }
71
87
 
72
- async ensureRepos({ repoName, createIfMissing = true, description = "Managed by git-mcpv2" }) {
88
+ async ensureRepos({ repoName, createIfMissing = true, description = "Managed by git-mcpv2", isPublic = false }) {
89
+ // Por padrão, repositórios são PRIVADOS. Use isPublic=true para público.
90
+ const isPrivate = !isPublic;
73
91
  const results = { github: null, gitea: null };
74
92
  // GitHub
75
93
  if (this.github) {
@@ -85,8 +103,8 @@ export class ProviderManager {
85
103
  const cr = await this.github.rest.repos.createForAuthenticatedUser({
86
104
  name: repoName,
87
105
  description,
88
- private: false,
89
- auto_init: true,
106
+ private: isPrivate,
107
+ auto_init: false,
90
108
  });
91
109
  results.github = { ok: true, repo: cr.data.full_name, created: true };
92
110
  } else {
@@ -113,7 +131,7 @@ export class ProviderManager {
113
131
  if (createIfMissing) {
114
132
  try {
115
133
  const cr = await axios.post(`${this.giteaUrl}/api/v1/user/repos`,
116
- { name: repoName, description, private: false, auto_init: true },
134
+ { name: repoName, description, private: isPrivate, auto_init: false },
117
135
  { headers: { Authorization: `token ${this.giteaToken}` }, timeout: 8000 });
118
136
  results.gitea = { ok: true, repo: `${cr.data?.owner?.login || owner}/${repoName}`, created: true };
119
137
  } catch (err) {
@@ -127,4 +145,59 @@ export class ProviderManager {
127
145
  }
128
146
  return results;
129
147
  }
148
+
149
+ async listAllRepos(options = {}) {
150
+ const { maxPages = 10, perPage = 100 } = options;
151
+ const results = { github: [], gitea: [] };
152
+
153
+ // GitHub - com paginação
154
+ if (this.github) {
155
+ try {
156
+ let page = 1;
157
+ let hasMore = true;
158
+ while (hasMore && page <= maxPages) {
159
+ const { data } = await this.github.rest.repos.listForAuthenticatedUser({
160
+ per_page: perPage,
161
+ page,
162
+ visibility: "all"
163
+ });
164
+ results.github.push(...data.map(r => ({
165
+ name: r.name,
166
+ fullName: r.full_name,
167
+ url: r.html_url,
168
+ isPrivate: r.private
169
+ })));
170
+ hasMore = data.length === perPage;
171
+ page++;
172
+ }
173
+ } catch (e) {
174
+ console.error("GitHub List Error:", e.message);
175
+ }
176
+ }
177
+
178
+ // Gitea - com paginação
179
+ if (this.giteaUrl && this.giteaToken) {
180
+ try {
181
+ let page = 1;
182
+ let hasMore = true;
183
+ const base = this.giteaUrl.replace(/\/$/, "");
184
+ while (hasMore && page <= maxPages) {
185
+ const { data } = await axios.get(`${base}/api/v1/user/repos?limit=${perPage}&page=${page}`, {
186
+ headers: { Authorization: `token ${this.giteaToken}` }
187
+ });
188
+ results.gitea.push(...data.map(r => ({
189
+ name: r.name,
190
+ fullName: r.full_name,
191
+ url: r.html_url,
192
+ isPrivate: r.private
193
+ })));
194
+ hasMore = data.length === perPage;
195
+ page++;
196
+ }
197
+ } catch (e) {
198
+ console.error("Gitea List Error:", e.message);
199
+ }
200
+ }
201
+ return results;
202
+ }
130
203
  }
@@ -0,0 +1,285 @@
1
+ import { asToolResult } from "../utils/errors.js";
2
+
3
+ /**
4
+ * Tool de ajuda para AI Agents - fornece orientação sobre qual tool usar
5
+ */
6
+ export function createGitHelpTool() {
7
+ const inputSchema = {
8
+ type: "object",
9
+ properties: {
10
+ query: {
11
+ type: "string",
12
+ description: "O que você quer fazer? Ex: 'criar repositório', 'fazer commit', 'criar branch'"
13
+ },
14
+ listTools: {
15
+ type: "boolean",
16
+ description: "Se true, lista todas as tools disponíveis com descrição resumida"
17
+ },
18
+ showFlows: {
19
+ type: "boolean",
20
+ description: "Se true, mostra fluxos de trabalho comuns"
21
+ }
22
+ },
23
+ additionalProperties: false
24
+ };
25
+
26
+ const TOOLS_SUMMARY = {
27
+ "git-workflow": {
28
+ purpose: "Operações básicas do dia-a-dia: init, status, add, commit, push",
29
+ actions: ["init", "status", "add", "remove", "commit", "push", "ensure-remotes", "clean"],
30
+ useWhen: "Começar projeto, salvar mudanças, enviar para GitHub/Gitea"
31
+ },
32
+ "git-branches": {
33
+ purpose: "Gerenciar branches: criar, listar, trocar, deletar",
34
+ actions: ["list", "create", "checkout", "delete", "rename"],
35
+ useWhen: "Trabalhar em features isoladas, organizar desenvolvimento"
36
+ },
37
+ "git-merge": {
38
+ purpose: "Juntar branches após desenvolvimento",
39
+ actions: ["merge", "abort", "status"],
40
+ useWhen: "Integrar feature branch na main/develop"
41
+ },
42
+ "git-stash": {
43
+ purpose: "Salvar mudanças temporariamente sem commit",
44
+ actions: ["list", "save", "apply", "pop", "drop", "clear"],
45
+ useWhen: "Trocar de branch com mudanças não commitadas"
46
+ },
47
+ "git-tags": {
48
+ purpose: "Marcar versões/releases",
49
+ actions: ["list", "create", "delete", "push"],
50
+ useWhen: "Publicar versão (v1.0.0, v2.0.0)"
51
+ },
52
+ "git-history": {
53
+ purpose: "Ver histórico de commits",
54
+ actions: ["log"],
55
+ useWhen: "Encontrar commit específico, ver quem alterou o quê"
56
+ },
57
+ "git-diff": {
58
+ purpose: "Ver diferenças entre versões",
59
+ actions: ["show", "compare", "stat"],
60
+ useWhen: "Revisar mudanças antes de commit, comparar branches"
61
+ },
62
+ "git-reset": {
63
+ purpose: "Desfazer commits ou mudanças",
64
+ actions: ["soft", "mixed", "hard", "hard-clean"],
65
+ useWhen: "Desfazer último commit, limpar working directory"
66
+ },
67
+ "git-config": {
68
+ purpose: "Configurar Git (nome, email, etc)",
69
+ actions: ["get", "set", "unset", "list"],
70
+ useWhen: "Configurar autor dos commits"
71
+ },
72
+ "git-remote": {
73
+ purpose: "Operações em GitHub/Gitea",
74
+ actions: ["list", "ensure", "release-create", "topics-set", "label-create"],
75
+ useWhen: "Criar release, configurar repositório remoto"
76
+ },
77
+ "git-sync": {
78
+ purpose: "Sincronizar com remotes",
79
+ actions: ["fetch", "pull"],
80
+ useWhen: "Baixar mudanças de outros desenvolvedores"
81
+ },
82
+ "git-clone": {
83
+ purpose: "Clonar repositório existente",
84
+ actions: ["clone"],
85
+ useWhen: "Baixar projeto existente do GitHub/Gitea"
86
+ },
87
+ "git-ignore": {
88
+ purpose: "Gerenciar .gitignore",
89
+ actions: ["list", "create", "add", "remove"],
90
+ useWhen: "Ignorar arquivos (node_modules, .env, etc)"
91
+ },
92
+ "git-files": {
93
+ purpose: "Ler arquivos do histórico Git",
94
+ actions: ["list", "read"],
95
+ useWhen: "Ver arquivo em versão antiga"
96
+ },
97
+ "git-issues": {
98
+ purpose: "Gerenciar issues no GitHub/Gitea",
99
+ actions: ["create", "list", "comment"],
100
+ useWhen: "Reportar bugs, criar tarefas"
101
+ },
102
+ "git-pulls": {
103
+ purpose: "Gerenciar Pull Requests",
104
+ actions: ["create", "list", "files"],
105
+ useWhen: "Propor mudanças para review"
106
+ }
107
+ };
108
+
109
+ const COMMON_FLOWS = {
110
+ "novo-projeto": {
111
+ description: "Criar repositório do zero",
112
+ steps: [
113
+ { tool: "git-workflow", action: "init", note: "Inicializa git e cria repos no GitHub/Gitea" },
114
+ { tool: "git-workflow", action: "add", params: { files: ["."] } },
115
+ { tool: "git-workflow", action: "commit", params: { message: "Initial commit" } },
116
+ { tool: "git-workflow", action: "push" }
117
+ ]
118
+ },
119
+ "salvar-mudancas": {
120
+ description: "Salvar e enviar mudanças",
121
+ steps: [
122
+ { tool: "git-workflow", action: "status", note: "Ver o que mudou" },
123
+ { tool: "git-workflow", action: "add", params: { files: ["."] } },
124
+ { tool: "git-workflow", action: "commit", params: { message: "sua mensagem" } },
125
+ { tool: "git-workflow", action: "push" }
126
+ ]
127
+ },
128
+ "nova-feature": {
129
+ description: "Desenvolver feature em branch separada",
130
+ steps: [
131
+ { tool: "git-branches", action: "create", params: { branch: "feature/nome" } },
132
+ { tool: "git-branches", action: "checkout", params: { branch: "feature/nome" } },
133
+ { note: "... fazer mudanças ..." },
134
+ { tool: "git-workflow", action: "add" },
135
+ { tool: "git-workflow", action: "commit" },
136
+ { tool: "git-branches", action: "checkout", params: { branch: "main" } },
137
+ { tool: "git-merge", action: "merge", params: { branch: "feature/nome" } },
138
+ { tool: "git-workflow", action: "push" }
139
+ ]
140
+ },
141
+ "criar-release": {
142
+ description: "Publicar nova versão",
143
+ steps: [
144
+ { tool: "git-tags", action: "create", params: { tag: "v1.0.0", message: "Release v1.0.0" } },
145
+ { tool: "git-tags", action: "push", params: { tag: "v1.0.0" } },
146
+ { tool: "git-remote", action: "release-create", params: { tag: "v1.0.0", name: "v1.0.0" } }
147
+ ]
148
+ },
149
+ "desfazer-commit": {
150
+ description: "Desfazer último commit mantendo mudanças",
151
+ steps: [
152
+ { tool: "git-reset", action: "soft", params: { ref: "HEAD~1" } }
153
+ ]
154
+ },
155
+ "trocar-branch-com-mudancas": {
156
+ description: "Salvar mudanças e trocar de branch",
157
+ steps: [
158
+ { tool: "git-stash", action: "save", params: { message: "WIP" } },
159
+ { tool: "git-branches", action: "checkout", params: { branch: "outra-branch" } },
160
+ { note: "... fazer o que precisar ..." },
161
+ { tool: "git-branches", action: "checkout", params: { branch: "branch-original" } },
162
+ { tool: "git-stash", action: "pop" }
163
+ ]
164
+ }
165
+ };
166
+
167
+ // Mapeamento de intenções para tools
168
+ const INTENT_MAP = {
169
+ // Palavras-chave -> tool recomendada
170
+ "criar reposit": "git-workflow (action=init)",
171
+ "iniciar projeto": "git-workflow (action=init)",
172
+ "novo projeto": "git-workflow (action=init)",
173
+ "commit": "git-workflow (action=commit)",
174
+ "salvar": "git-workflow (action=add + commit)",
175
+ "push": "git-workflow (action=push)",
176
+ "enviar": "git-workflow (action=push)",
177
+ "branch": "git-branches",
178
+ "criar branch": "git-branches (action=create)",
179
+ "trocar branch": "git-branches (action=checkout)",
180
+ "merge": "git-merge",
181
+ "juntar": "git-merge",
182
+ "stash": "git-stash",
183
+ "guardar": "git-stash (action=save)",
184
+ "tag": "git-tags",
185
+ "versão": "git-tags",
186
+ "release": "git-remote (action=release-create)",
187
+ "histórico": "git-history",
188
+ "log": "git-history",
189
+ "diff": "git-diff",
190
+ "diferença": "git-diff",
191
+ "desfazer": "git-reset",
192
+ "reset": "git-reset",
193
+ "clone": "git-clone",
194
+ "clonar": "git-clone",
195
+ "baixar projeto": "git-clone",
196
+ "issue": "git-issues",
197
+ "bug": "git-issues (action=create)",
198
+ "pull request": "git-pulls",
199
+ "pr": "git-pulls",
200
+ "ignorar": "git-ignore",
201
+ "gitignore": "git-ignore",
202
+ "config": "git-config",
203
+ "configurar": "git-config",
204
+ "status": "git-workflow (action=status)",
205
+ "ver mudanças": "git-workflow (action=status)"
206
+ };
207
+
208
+ const description = `Ajuda para AI Agents - descobre qual tool usar para cada situação.
209
+
210
+ USE ESTA TOOL QUANDO:
211
+ - Não sabe qual git-* tool usar
212
+ - Precisa ver fluxos de trabalho comuns
213
+ - Quer lista de todas as tools disponíveis
214
+
215
+ EXEMPLOS:
216
+ • { "query": "como criar um repositório?" }
217
+ • { "listTools": true }
218
+ • { "showFlows": true }`;
219
+
220
+ async function handle(args) {
221
+ const { query, listTools, showFlows } = args || {};
222
+
223
+ // Listar todas as tools
224
+ if (listTools) {
225
+ return asToolResult({
226
+ tools: TOOLS_SUMMARY,
227
+ tip: "Use query='o que você quer fazer' para recomendação específica"
228
+ });
229
+ }
230
+
231
+ // Mostrar fluxos comuns
232
+ if (showFlows) {
233
+ return asToolResult({
234
+ flows: COMMON_FLOWS,
235
+ tip: "Siga os steps em ordem para cada fluxo"
236
+ });
237
+ }
238
+
239
+ // Buscar por query
240
+ if (query) {
241
+ const q = query.toLowerCase();
242
+ const matches = [];
243
+
244
+ for (const [intent, tool] of Object.entries(INTENT_MAP)) {
245
+ if (q.includes(intent) || intent.includes(q)) {
246
+ matches.push({ intent, recommendation: tool });
247
+ }
248
+ }
249
+
250
+ if (matches.length > 0) {
251
+ return asToolResult({
252
+ query,
253
+ recommendations: matches,
254
+ tip: "Use a tool recomendada com os parâmetros indicados"
255
+ });
256
+ }
257
+
258
+ return asToolResult({
259
+ query,
260
+ message: "Não encontrei recomendação específica. Veja todas as tools:",
261
+ tools: Object.keys(TOOLS_SUMMARY),
262
+ tip: "Use listTools=true para ver detalhes de cada tool"
263
+ });
264
+ }
265
+
266
+ // Sem parâmetros - retornar overview
267
+ return asToolResult({
268
+ message: "Git MCP - Tools para AI Agents",
269
+ availableTools: Object.keys(TOOLS_SUMMARY).length,
270
+ usage: {
271
+ "Recomendação": "{ query: 'o que você quer fazer' }",
272
+ "Listar tools": "{ listTools: true }",
273
+ "Ver fluxos": "{ showFlows: true }"
274
+ },
275
+ quickStart: "Para começar um projeto: git-workflow com action='init'"
276
+ });
277
+ }
278
+
279
+ return {
280
+ name: "git-help",
281
+ description,
282
+ inputSchema,
283
+ handle
284
+ };
285
+ }
@@ -10,22 +10,24 @@ export function createGitRemoteTool(pm, git) {
10
10
  const inputSchema = {
11
11
  type: "object",
12
12
  properties: {
13
- projectPath: {
13
+ projectPath: {
14
14
  type: "string",
15
15
  description: "Caminho absoluto do diretório do projeto"
16
16
  },
17
- action: {
18
- type: "string",
17
+ action: {
18
+ type: "string",
19
19
  enum: [
20
- "list", "ensure", "repo-delete",
20
+ "list", "list-all", "ensure", "repo-delete",
21
21
  "release-create", "topics-set", "milestone-create", "label-create",
22
22
  "fork-create", "fork-list", "star-set", "star-unset",
23
23
  "subscription-set", "subscription-unset", "contents-create"
24
24
  ],
25
25
  description: `Ação a executar:
26
26
 
27
+ CONFIGURAÇÃO:
27
28
  CONFIGURAÇÃO:
28
29
  - list: Lista remotes configurados (github, gitea, origin)
30
+ - list-all: Lista TODOS os repositórios da conta (GitHub + Gitea)
29
31
  - ensure: Cria repos no GitHub/Gitea e configura remotes (USE ESTE se push falhar)
30
32
 
31
33
  REPOSITÓRIO:
@@ -48,32 +50,32 @@ NOTIFICAÇÕES:
48
50
  ARQUIVOS VIA API:
49
51
  - contents-create: Cria arquivo diretamente via API (sem git local)`
50
52
  },
51
- tag: {
53
+ tag: {
52
54
  type: "string",
53
55
  description: "Tag para release-create. Ex: 'v1.0.0'"
54
56
  },
55
- name: {
57
+ name: {
56
58
  type: "string",
57
59
  description: "Nome da release, label, etc."
58
60
  },
59
- body: {
61
+ body: {
60
62
  type: "string",
61
63
  description: "Descrição/corpo da release"
62
64
  },
63
- topics: {
64
- type: "array",
65
+ topics: {
66
+ type: "array",
65
67
  items: { type: "string" },
66
68
  description: "Lista de tópicos para topics-set. Ex: ['javascript', 'nodejs', 'mcp']"
67
69
  },
68
- path: {
70
+ path: {
69
71
  type: "string",
70
72
  description: "Caminho do arquivo para contents-create"
71
73
  },
72
- content: {
74
+ content: {
73
75
  type: "string",
74
76
  description: "Conteúdo do arquivo para contents-create"
75
77
  },
76
- branch: {
78
+ branch: {
77
79
  type: "string",
78
80
  description: "Branch alvo para contents-create. Default: main"
79
81
  },
@@ -84,6 +86,10 @@ ARQUIVOS VIA API:
84
86
  title: {
85
87
  type: "string",
86
88
  description: "Título do milestone"
89
+ },
90
+ isPublic: {
91
+ type: "boolean",
92
+ description: "Se true, repositório será PÚBLICO. Default: false (privado). Aplica-se a action='ensure'"
87
93
  }
88
94
  },
89
95
  required: ["projectPath", "action"],
@@ -113,13 +119,13 @@ QUANDO USAR:
113
119
  validateProjectPath(projectPath);
114
120
  if (action === "list") {
115
121
  const remotes = await git.listRemotes(projectPath);
116
-
122
+
117
123
  // Debug info: calculate what URLs should be
118
124
  let repoName = getRepoNameFromPath(projectPath);
119
125
  if (repoName === "GIT_MCP") repoName = "git-mcp";
120
126
  const calculated = await pm.getRemoteUrls(repoName);
121
127
 
122
- return asToolResult({
128
+ return asToolResult({
123
129
  remotes,
124
130
  configured: remotes.length > 0,
125
131
  hasGithub: remotes.some(r => r.remote === "github"),
@@ -136,9 +142,24 @@ QUANDO USAR:
136
142
  message: remotes.length === 0 ? "Nenhum remote configurado. Use action='ensure' para configurar." : undefined
137
143
  });
138
144
  }
145
+
146
+ if (action === "list-all") {
147
+ const repos = await pm.listAllRepos();
148
+ const summary = [];
149
+ if (repos.github?.length) summary.push(`${repos.github.length} GitHub repos`);
150
+ if (repos.gitea?.length) summary.push(`${repos.gitea.length} Gitea repos`);
151
+
152
+ return asToolResult({
153
+ success: true,
154
+ summary: summary.join(", "),
155
+ github: repos.github,
156
+ gitea: repos.gitea
157
+ });
158
+ }
139
159
  if (action === "ensure") {
140
160
  const repo = getRepoNameFromPath(projectPath);
141
- const ensured = await pm.ensureRepos({ repoName: repo, createIfMissing: true });
161
+ const isPublic = args.isPublic === true; // Default: privado
162
+ const ensured = await pm.ensureRepos({ repoName: repo, createIfMissing: true, isPublic });
142
163
  const ghOwner = await pm.getGitHubOwner();
143
164
  const geOwner = await pm.getGiteaOwner();
144
165
  const githubUrl = ghOwner ? `https://github.com/${ghOwner}/${repo}.git` : "";
@@ -146,9 +167,10 @@ QUANDO USAR:
146
167
  const giteaUrl = geOwner && base ? `${base}/${geOwner}/${repo}.git` : "";
147
168
  await git.ensureRemotes(projectPath, { githubUrl, giteaUrl });
148
169
  const remotes = await git.listRemotes(projectPath);
149
- return asToolResult({
150
- success: true,
151
- ensured,
170
+ return asToolResult({
171
+ success: true,
172
+ isPrivate: !isPublic,
173
+ ensured,
152
174
  remotes,
153
175
  urls: { github: githubUrl, gitea: giteaUrl },
154
176
  message: "Remotes configurados. Agora pode usar git-workflow push."
@@ -257,8 +279,8 @@ QUANDO USAR:
257
279
  });
258
280
  return asToolResult({ success: !!(out.github?.ok || out.gitea?.ok), path: filePath, branch, providers: out });
259
281
  }
260
- return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, {
261
- availableActions: ["list", "ensure", "repo-delete", "release-create", "topics-set", "milestone-create", "label-create", "fork-create", "fork-list", "star-set", "star-unset", "subscription-set", "subscription-unset", "contents-create"]
282
+ return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, {
283
+ availableActions: ["list", "ensure", "repo-delete", "release-create", "topics-set", "milestone-create", "label-create", "fork-create", "fork-list", "star-set", "star-unset", "subscription-set", "subscription-unset", "contents-create"]
262
284
  });
263
285
  } catch (e) {
264
286
  return errorToResponse(e);