@andre.buzeli/git-mcp 16.0.6 → 16.1.2

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.
@@ -1,6 +1,7 @@
1
1
  import Ajv from "ajv";
2
- import { asToolError, asToolResult, errorToResponse, createError } from "../utils/errors.js";
3
- import { validateProjectPath, withRetry } from "../utils/repoHelpers.js";
2
+ import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
3
+ import { validateProjectPath } from "../utils/repoHelpers.js";
4
+ import { withRetry } from "../utils/retry.js";
4
5
 
5
6
  const ajv = new Ajv({ allErrors: true });
6
7
 
@@ -10,128 +11,90 @@ export function createGitCloneTool(git) {
10
11
  properties: {
11
12
  projectPath: {
12
13
  type: "string",
13
- description: "Caminho absoluto onde o repositório será clonado"
14
+ description: "Caminho absoluto do DIRETÓRIO PAI onde o repositório será clonado. O nome da pasta será o nome do repo, a menos que especificado."
14
15
  },
15
16
  action: {
16
17
  type: "string",
17
18
  enum: ["clone"],
18
- description: "Ação a executar: clone - clona um repositório remoto"
19
+ description: "Ação a executar: clone"
19
20
  },
20
21
  url: {
21
22
  type: "string",
22
- description: "URL do repositório para clonar. Ex: 'https://github.com/user/repo.git'"
23
+ description: "URL do repositório Git (HTTPS ou SSH)"
24
+ },
25
+ name: {
26
+ type: "string",
27
+ description: "Nome da pasta de destino (opcional). Se não informado, usa o nome do repo da URL"
23
28
  },
24
29
  branch: {
25
30
  type: "string",
26
- description: "Branch específica para clonar. Default: branch padrão do repo"
31
+ description: "Branch específica para clonar (opcional)"
27
32
  },
28
- depth: {
33
+ depth: {
29
34
  type: "number",
30
- description: "Profundidade do clone (shallow clone). Ex: 1 para último commit apenas"
31
- },
32
- singleBranch: {
33
- type: "boolean",
34
- description: "Se true, clona apenas a branch especificada. Default: false"
35
+ description: "Profundidade do clone (shallow clone). Ex: 1 para apenas o último commit"
35
36
  }
36
37
  },
37
38
  required: ["projectPath", "action", "url"],
38
39
  additionalProperties: false
39
40
  };
40
41
 
41
- const description = `Clonar repositórios remotos.
42
-
43
- QUANDO USAR:
44
- - Baixar um repositório existente do GitHub/Gitea
45
- - Iniciar trabalho em um projeto existente
46
- - Criar cópia local de um repositório
42
+ const description = `IMPORTANTE projectPath:
43
+ Informe o caminho absoluto do projeto aberto no IDE. O servidor MCP não tem acesso ao
44
+ contexto do IDE e não consegue detectar automaticamente qual projeto está sendo trabalhado.
47
45
 
48
- EXEMPLOS:
49
- - Clone básico: action='clone' url='https://github.com/user/repo.git'
50
- - Clone shallow: action='clone' url='...' depth=1
51
- - Clone de branch: action='clone' url='...' branch='develop'
46
+ Clonagem de repositórios Git.
52
47
 
53
- AUTENTICAÇÃO:
54
- - Repos públicos: Não precisa de token
55
- - Repos privados: Configure GITHUB_TOKEN ou GITEA_TOKEN
48
+ QUANDO USAR:
49
+ - Baixar um projeto existente do GitHub/Gitea
50
+ - Iniciar trabalho em um novo repo
56
51
 
57
- NOTAS:
58
- - O diretório de destino será criado automaticamente
59
- - Se existir arquivos, o clone falhará`;
52
+ EXEMPLO:
53
+ - Clonar repo: action='clone' url='https://github.com/user/repo.git'
54
+ - Clonar em pasta específica: action='clone' url='...' name='minha-pasta'
55
+ - Shallow clone (rápido): action='clone' url='...' depth=1`;
60
56
 
61
57
  async function handle(args) {
62
58
  const validate = ajv.compile(inputSchema);
63
59
  if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
60
+
61
+ // projectPath aqui é o diretório PAI
64
62
  const { projectPath, action, url } = args;
65
63
 
66
64
  try {
67
- // Validação de path
65
+ // Validar se diretório pai existe
66
+ // validateProjectPath verifica se existe.
68
67
  validateProjectPath(projectPath);
69
68
 
70
69
  if (action === "clone") {
71
- if (!url) {
72
- return asToolError("MISSING_PARAMETER", "url é obrigatório para clone", {
73
- parameter: "url",
74
- example: "https://github.com/user/repo.git"
75
- });
76
- }
70
+ await withRetry(() => git.clone(projectPath, url, {
71
+ name: args.name,
72
+ branch: args.branch,
73
+ depth: args.depth
74
+ }), 3, "git-clone");
77
75
 
78
- // Idempotency check
79
- if (fs.existsSync(path.join(projectPath, ".git"))) {
80
- try {
81
- // Check if it's the same repo
82
- // git.getRemoteUrl is not standard in adapter, use exec or listRemotes logic
83
- // Assuming git.listRemotes or similar exists, or we catch the error
84
- // Let's try to infer from config or just warn
85
-
86
- // We'll rely on git status to check if it's healthy
87
- await git.status(projectPath);
88
-
89
- return asToolResult({
90
- success: true,
91
- url,
92
- path: projectPath,
93
- branch: args.branch || "current",
94
- message: `Repositório já existe em '${projectPath}'. Clone ignorado (idempotente).`,
95
- nextStep: "Use git-workflow status para ver o estado atual"
96
- });
97
- } catch (e) {
98
- // If status fails, maybe it's broken
99
- console.warn("Existing repo check failed:", e);
100
- }
101
- } else if (fs.existsSync(projectPath) && fs.readdirSync(projectPath).length > 0) {
102
- return asToolError("DIR_NOT_EMPTY", `Diretório '${projectPath}' existe e não está vazio`, {
103
- suggestion: "Use um diretório novo ou limpe o atual"
104
- });
105
- }
106
-
107
- const result = await withRetry(
108
- () => git.clone(url, projectPath, {
109
- branch: args.branch,
110
- depth: args.depth,
111
- singleBranch: args.singleBranch
112
- }),
113
- 3,
114
- "clone"
115
- );
76
+ const repoName = args.name || url.split("/").pop().replace(".git", "");
77
+ const finalPath = `${projectPath}/${repoName}`.replace("//", "/");
116
78
 
117
- return asToolResult({
118
- success: true,
119
- url,
120
- path: projectPath,
121
- branch: result.branch || args.branch || "default",
122
- ...result,
123
- message: `Repositório clonado com sucesso em '${projectPath}'`,
124
- nextStep: "Use git-workflow status para ver o estado do repositório"
79
+ return asToolResult({
80
+ success: true,
81
+ message: `Repositório clonado com sucesso em '${finalPath}'`,
82
+ path: finalPath,
83
+ url
125
84
  });
126
85
  }
127
86
 
128
- return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, {
129
- availableActions: ["clone"]
130
- });
87
+ return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, { availableActions: ["clone"] });
131
88
  } catch (e) {
132
89
  return errorToResponse(e);
133
90
  }
134
91
  }
135
92
 
136
- return { name: "git-clone", description, inputSchema, handle };
93
+ return {
94
+ name: "git-clone",
95
+ description,
96
+ inputSchema,
97
+ handle,
98
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false }
99
+ };
137
100
  }
@@ -11,7 +11,7 @@ export function createGitConfigTool(git) {
11
11
  properties: {
12
12
  projectPath: {
13
13
  type: "string",
14
- description: "Caminho absoluto do diretório do projeto"
14
+ description: "Caminho absoluto do diretório do projeto no IDE (ex: '/home/user/meu-projeto' ou 'C:/Users/user/meu-projeto'). IMPORTANTE: este valor não pode ser inferido automaticamente pelo servidor — o agente deve fornecer o path real do projeto sendo trabalhado, não o diretório home do usuário."
15
15
  },
16
16
  action: {
17
17
  type: "string",
@@ -40,7 +40,11 @@ export function createGitConfigTool(git) {
40
40
  additionalProperties: false
41
41
  };
42
42
 
43
- const description = `Gerenciamento de configurações Git.
43
+ const description = `IMPORTANTE projectPath:
44
+ Informe o caminho absoluto do projeto aberto no IDE. O servidor MCP não tem acesso ao
45
+ contexto do IDE e não consegue detectar automaticamente qual projeto está sendo trabalhado.
46
+
47
+ Gerenciamento de configurações Git.
44
48
 
45
49
  CONFIGURAÇÕES COMUNS:
46
50
  - user.name: Nome do autor dos commits
@@ -90,5 +94,11 @@ EXEMPLOS:
90
94
  }
91
95
  }
92
96
 
93
- return { name: "git-config", description, inputSchema, handle };
97
+ return {
98
+ name: "git-config",
99
+ description,
100
+ inputSchema,
101
+ handle,
102
+ annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true }
103
+ };
94
104
  }
@@ -1,137 +1,121 @@
1
- import Ajv from "ajv";
2
- import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
3
- import { validateProjectPath, withRetry } from "../utils/repoHelpers.js";
4
-
5
- const ajv = new Ajv({ allErrors: true });
6
-
7
- export function createGitDiffTool(git) {
8
- const inputSchema = {
9
- type: "object",
10
- properties: {
11
- projectPath: {
12
- type: "string",
13
- description: "Caminho absoluto do diretório do projeto"
14
- },
15
- action: {
16
- type: "string",
17
- enum: ["show", "compare", "stat"],
18
- description: `Ação a executar:
19
- - show: Mostra diff do working directory (arquivos não commitados)
20
- - compare: Compara dois commits/branches/tags
21
- - stat: Mostra estatísticas (arquivos alterados, linhas +/-)`
22
- },
23
- from: {
24
- type: "string",
25
- description: "Ref inicial para comparação (commit SHA, branch, tag). Default: HEAD"
26
- },
27
- fromRef: {
28
- type: "string",
29
- description: "Alias para 'from'. Ref inicial para comparação"
30
- },
31
- to: {
32
- type: "string",
33
- description: "Ref final para comparação. Se não fornecido, compara com working directory"
34
- },
35
- toRef: {
36
- type: "string",
37
- description: "Alias para 'to'. Ref final para comparação"
38
- },
39
- file: {
40
- type: "string",
41
- description: "Arquivo específico para ver diff. Se não fornecido, mostra todos"
42
- },
43
- filepath: {
44
- type: "string",
45
- description: "Alias para 'file'. Arquivo específico para ver diff"
46
- },
47
- context: {
48
- type: "number",
49
- description: "Número de linhas de contexto ao redor das mudanças. Default: 3"
50
- }
51
- },
52
- required: ["projectPath", "action"],
53
- additionalProperties: false
54
- };
55
-
56
- const description = `Ver diferenças entre commits, branches ou arquivos.
57
-
58
- QUANDO USAR:
59
- - Ver o que mudou antes de commitar (action='show')
60
- - Comparar branches antes de merge (action='compare')
61
- - Ver estatísticas de mudanças (action='stat')
62
-
63
- EXEMPLOS:
64
- - Ver mudanças não commitadas: action='show'
65
- - Ver mudanças de um arquivo: action='show' file='src/index.js'
66
- - Comparar com último commit: action='compare' from='HEAD~1'
67
- - Comparar branches: action='compare' from='main' to='feature/x'
68
- - Ver stats: action='stat' from='main' to='develop'`;
69
-
70
- async function handle(args) {
71
- const validate = ajv.compile(inputSchema);
72
- if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
73
- const { projectPath, action } = args;
74
-
75
- // Support aliases: fromRef -> from, toRef -> to, filepath -> file
76
- const fromParam = args.from || args.fromRef || "HEAD";
77
- const toParam = args.to || args.toRef;
78
- const fileParam = args.file || args.filepath;
79
-
80
- try {
81
- validateProjectPath(projectPath);
82
- if (action === "show") {
83
- const diff = await withRetry(() => git.diff(projectPath, {
84
- file: fileParam,
85
- context: args.context || 3
86
- }), 3, "diff-show");
87
-
88
- if (!diff || diff.length === 0) {
89
- return asToolResult({
90
- changes: [],
91
- message: "Nenhuma mudança detectada no working directory"
92
- });
93
- }
94
-
95
- return asToolResult({
96
- changes: diff,
97
- totalFiles: diff.length,
98
- message: `${diff.length} arquivo(s) com mudanças`
99
- });
100
- }
101
-
102
- if (action === "compare") {
103
- const diff = await withRetry(() => git.diffCommits(projectPath, fromParam, toParam, {
104
- file: fileParam,
105
- context: args.context || 3
106
- }), 3, "diff-compare");
107
-
108
- return asToolResult({
109
- from: fromParam,
110
- to: toParam || "working directory",
111
- changes: diff,
112
- totalFiles: diff.length,
113
- message: `Comparando ${fromParam} com ${toParam || 'working directory'}: ${diff.length} arquivo(s)`
114
- });
115
- }
116
-
117
- if (action === "stat") {
118
- const stats = await withRetry(() => git.diffStats(projectPath, fromParam, toParam), 3, "diff-stat");
119
-
120
- return asToolResult({
121
- from: fromParam,
122
- to: toParam || "working directory",
123
- ...stats,
124
- message: `${stats.filesChanged} arquivo(s), +${stats.insertions} -${stats.deletions}`
125
- });
126
- }
127
-
128
- return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, {
129
- availableActions: ["show", "compare", "stat"]
130
- });
131
- } catch (e) {
132
- return errorToResponse(e);
133
- }
134
- }
135
-
136
- return { name: "git-diff", description, inputSchema, handle };
137
- }
1
+ import Ajv from "ajv";
2
+ import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
3
+ import { validateProjectPath } from "../utils/repoHelpers.js";
4
+
5
+ const ajv = new Ajv({ allErrors: true });
6
+
7
+ export function createGitDiffTool(git) {
8
+ const inputSchema = {
9
+ type: "object",
10
+ properties: {
11
+ projectPath: {
12
+ type: "string",
13
+ description: "Caminho absoluto do diretório do projeto no IDE (ex: '/home/user/meu-projeto' ou 'C:/Users/user/meu-projeto'). IMPORTANTE: este valor não pode ser inferido automaticamente pelo servidor — o agente deve fornecer o path real do projeto sendo trabalhado, não o diretório home do usuário."
14
+ },
15
+ action: {
16
+ type: "string",
17
+ enum: ["show", "compare", "stat"],
18
+ description: `Ação a executar:
19
+ - show: Mostra diferenças não commitadas (working tree vs index/HEAD)
20
+ - compare: Compara duas referências (branch vs branch, commit vs commit)
21
+ - stat: Mostra estatísticas resumidas (arquivos alterados, inserções, deleções)`
22
+ },
23
+ target: {
24
+ type: "string",
25
+ description: "Alvo da comparação (para action='compare'). Ex: 'HEAD', 'main', 'develop', 'abc1234'"
26
+ },
27
+ source: {
28
+ type: "string",
29
+ description: "Origem da comparação (para action='compare'). Default: HEAD atual"
30
+ },
31
+ staged: {
32
+ type: "boolean",
33
+ description: "Para action='show': comparar staged vs HEAD (em vez de working vs index). Default: false"
34
+ }
35
+ },
36
+ required: ["projectPath", "action"],
37
+ additionalProperties: false
38
+ };
39
+
40
+ const description = `IMPORTANTE — projectPath:
41
+ Informe o caminho absoluto do projeto aberto no IDE. O servidor MCP não tem acesso ao
42
+ contexto do IDE e não consegue detectar automaticamente qual projeto está sendo trabalhado.
43
+
44
+ Visualização de diferenças (diff) no Git.
45
+
46
+ QUANDO USAR:
47
+ - Revisar mudanças antes de commitar (action='show')
48
+ - Comparar branches antes de merge (action='compare')
49
+ - Ver o que mudou entre versões
50
+
51
+ EXEMPLOS:
52
+ - Ver mudanças não commitadas: action='show'
53
+ - Ver mudanças staged: action='show' staged=true
54
+ - Comparar branch atual com main: action='compare' target='main'
55
+ - Estatísticas de mudança: action='stat' target='HEAD~1'`;
56
+
57
+ async function handle(args) {
58
+ const validate = ajv.compile(inputSchema);
59
+ if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
60
+ const { projectPath, action } = args;
61
+
62
+ try {
63
+ validateProjectPath(projectPath);
64
+
65
+ if (action === "show") {
66
+ const diff = await git.diff(projectPath, { staged: args.staged });
67
+ return asToolResult({
68
+ diff: diff || "Nenhuma diferença encontrada",
69
+ staged: !!args.staged
70
+ });
71
+ }
72
+
73
+ if (action === "compare") {
74
+ if (!args.target) return asToolError("MISSING_PARAMETER", "target é obrigatório para compare", { parameter: "target", example: "main" });
75
+ const diff = await git.diff(projectPath, { target: args.target, source: args.source });
76
+ return asToolResult({
77
+ diff: diff || "Nenhuma diferença encontrada",
78
+ target: args.target,
79
+ source: args.source || "HEAD"
80
+ });
81
+ }
82
+
83
+ if (action === "stat") {
84
+ // Implementação simplificada de diff --stat via git log ou diff
85
+ // Se target fornecido, compara HEAD com target. Se não, compara working com HEAD
86
+ // gitAdapter.diff já retorna texto. Para stat, precisaríamos parsear ou usar flag.
87
+ // O adaptador atual talvez não suporte --stat diretamente.
88
+ // Vamos retornar o diff normal com uma nota, ou tentar implementar se suportado.
89
+ // Assumindo que git.diff suporta options string ou object.
90
+
91
+ // Se o adaptador não suportar stat nativamente, retornamos o diff normal
92
+ const diff = await git.diff(projectPath, { target: args.target, source: args.source });
93
+
94
+ // Tentar gerar estatísticas simples do diff text
95
+ const filesChanged = (diff.match(/^diff --git/gm) || []).length;
96
+ const insertions = (diff.match(/^\+/gm) || []).length;
97
+ const deletions = (diff.match(/^-/gm) || []).length;
98
+
99
+ return asToolResult({
100
+ summary: `${filesChanged} arquivos alterados, ${insertions} inserções(+), ${deletions} deleções(-)`,
101
+ filesChanged,
102
+ insertions,
103
+ deletions,
104
+ target: args.target
105
+ });
106
+ }
107
+
108
+ return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, { availableActions: ["show", "compare", "stat"] });
109
+ } catch (e) {
110
+ return errorToResponse(e);
111
+ }
112
+ }
113
+
114
+ return {
115
+ name: "git-diff",
116
+ description,
117
+ inputSchema,
118
+ handle,
119
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true }
120
+ };
121
+ }
@@ -10,7 +10,7 @@ export function createGitFilesTool(git) {
10
10
  properties: {
11
11
  projectPath: {
12
12
  type: "string",
13
- description: "Caminho absoluto do diretório do projeto"
13
+ description: "Caminho absoluto do diretório do projeto no IDE (ex: '/home/user/meu-projeto' ou 'C:/Users/user/meu-projeto'). IMPORTANTE: este valor não pode ser inferido automaticamente pelo servidor — o agente deve fornecer o path real do projeto sendo trabalhado, não o diretório home do usuário."
14
14
  },
15
15
  action: {
16
16
  type: "string",
@@ -32,7 +32,11 @@ export function createGitFilesTool(git) {
32
32
  additionalProperties: false
33
33
  };
34
34
 
35
- const description = `Leitura de arquivos do repositório Git.
35
+ const description = `IMPORTANTE projectPath:
36
+ Informe o caminho absoluto do projeto aberto no IDE. O servidor MCP não tem acesso ao
37
+ contexto do IDE e não consegue detectar automaticamente qual projeto está sendo trabalhado.
38
+
39
+ Leitura de arquivos do repositório Git.
36
40
 
37
41
  QUANDO USAR:
38
42
  - Ver arquivos de um commit antigo
@@ -78,5 +82,11 @@ NOTA: Esta tool lê arquivos do histórico Git, não do sistema de arquivos.`;
78
82
  }
79
83
  }
80
84
 
81
- return { name: "git-files", description, inputSchema, handle };
85
+ return {
86
+ name: "git-files",
87
+ description,
88
+ inputSchema,
89
+ handle,
90
+ annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true }
91
+ };
82
92
  }