@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 +46 -1
- package/package.json +17 -2
- package/src/index.js +7 -1
- package/src/providers/providerManager.js +78 -5
- package/src/tools/git-help.js +285 -0
- package/src/tools/git-remote.js +42 -20
- package/src/tools/git-workflow.js +142 -13
- package/src/utils/env.js +104 -0
- package/src/utils/errors.js +137 -3
- package/src/utils/gitAdapter.js +304 -24
- package/src/utils/hooks.js +255 -0
- package/src/utils/metrics.js +198 -0
- package/src/utils/providerExec.js +50 -5
- package/src/utils/repoHelpers.js +13 -5
- package/src/utils/retry.js +68 -3
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
|
-
##
|
|
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.
|
|
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.
|
|
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:
|
|
89
|
-
auto_init:
|
|
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:
|
|
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
|
+
}
|
package/src/tools/git-remote.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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);
|