@andre.buzeli/git-mcp 16.1.4 → 16.1.7

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
@@ -17,10 +17,11 @@ Servidor MCP (Model Context Protocol) para operações Git locais sem git instal
17
17
  }
18
18
  }
19
19
  }
20
- }
20
+ }
21
21
  ```
22
22
 
23
- ## Tools
23
+ ## Tools
24
+
24
25
 
25
26
  - git-workflow: init, status, add, remove, commit, ensure-remotes, push
26
27
  - git-update: status -> add -> commit -> push (all-in-one)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andre.buzeli/git-mcp",
3
- "version": "16.1.4",
3
+ "version": "16.1.7",
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",
package/src/index.js CHANGED
@@ -59,53 +59,44 @@ const server = new Server(
59
59
  },
60
60
  instructions: `# git-mcp — Instruções para AI Agents
61
61
 
62
- ## REGRA #1: projectPath é OBRIGATÓRIO
63
- Toda tool (exceto git-help) exige "projectPath" com o caminho absoluto do projeto aberto no IDE.
64
- O servidor NÃO consegue detectar o projeto automaticamente.
65
- Exemplo: "projectPath": "C:/Users/user/meu-projeto" ou "/home/user/meu-projeto"
66
-
67
- ## REGRA #2: action é OBRIGATÓRIO
68
- Toda tool usa o padrão { "projectPath": "...", "action": "nome-da-action", ...params }.
69
- Consulte o enum de cada tool para saber as actions disponíveis.
70
-
71
- ## Guia Rápido de Tools
72
-
73
- | Necessidade | Tool | Action | Params obrigatórios |
74
- |-------------|------|--------|---------------------|
75
- | Atualizar projeto completo | git-workflow | update | message |
76
- | Ver status do repo | git-workflow | status | - |
77
- | Inicializar repo novo | git-workflow | init | - |
78
- | Ver diferenças locais | git-diff | show | - |
79
- | Comparar branches/commits | git-diff | compare | target |
80
- | Estatísticas de diff | git-diff | stat | - |
81
- | Criar branch | git-branches | create | branch |
82
- | Trocar branch | git-branches | checkout | branch |
83
- | Listar branches | git-branches | list | - |
84
- | Merge de branch | git-merge | merge | branch |
85
- | Criar tag | git-tags | create | tag |
86
- | Clonar repo | git-clone | clone | url |
87
- | Criar issue | git-issues | create | title |
88
- | Criar PR | git-pulls | create | head, base |
89
- | Salvar stash | git-stash | save | - |
90
- | Restaurar stash | git-stash | pop | - |
91
- | Reset commits | git-reset | soft/mixed/hard | ref |
92
- | Configurar git | git-config | get/set | key |
93
- | Gerenciar .gitignore | git-ignore | add/list | patterns |
94
- | Ler arquivo do git | git-files | read | filepath |
95
- | Ver histórico | git-history | log | - |
96
- | Fetch/Pull remoto | git-sync | fetch/pull | - |
97
- | Gerenciar remotes | git-remote | list/ensure | - |
98
- | Criar release | git-remote | release-create | tag |
99
- | Gerenciar worktrees | git-worktree | add/list/remove | branch (add) |
100
- | Ajuda sobre tools | git-help | - | query/listTools/showFlows |
101
-
102
- ## Exemplo Completo (ação mais comum)
103
- \`\`\`json
104
- { "projectPath": "/caminho/do/projeto", "action": "update", "message": "feat: descrição das mudanças" }
105
- \`\`\`
106
-
107
- ## Dica
108
- Use git-help action="listTools" para ver todas as tools e actions disponíveis em tempo real.`
62
+ ATENÇÃO: NUNCA chame uma tool sem os parâmetros obrigatórios. Toda call incompleta retorna erro.
63
+
64
+ ## Parâmetros obrigatórios em TODAS as tools (exceto git-help)
65
+ - "projectPath": caminho absoluto do projeto no sistema de arquivos do usuário. O servidor NÃO detecta automaticamente.
66
+ - "action": string com o nome da action a executar (ver enum no schema de cada tool)
67
+
68
+ ## Exemplos prontos para uso copie e substitua os valores
69
+
70
+ git-workflow (update): {"projectPath":"<PATH>","action":"update","message":"feat: descrição"}
71
+ git-workflow (status): {"projectPath":"<PATH>","action":"status"}
72
+ git-workflow (init): {"projectPath":"<PATH>","action":"init"}
73
+ git-workflow (push): {"projectPath":"<PATH>","action":"push"}
74
+ git-diff (show): {"projectPath":"<PATH>","action":"show"}
75
+ git-diff (compare): {"projectPath":"<PATH>","action":"compare","target":"main"}
76
+ git-branches (list): {"projectPath":"<PATH>","action":"list"}
77
+ git-branches (create): {"projectPath":"<PATH>","action":"create","branch":"feature/x"}
78
+ git-branches (checkout):{"projectPath":"<PATH>","action":"checkout","branch":"main"}
79
+ git-merge (merge): {"projectPath":"<PATH>","action":"merge","branch":"feature/x"}
80
+ git-tags (create): {"projectPath":"<PATH>","action":"create","tag":"v1.0.0"}
81
+ git-tags (push): {"projectPath":"<PATH>","action":"push","tag":"v1.0.0"}
82
+ git-stash (save): {"projectPath":"<PATH>","action":"save"}
83
+ git-stash (pop): {"projectPath":"<PATH>","action":"pop"}
84
+ git-reset (soft): {"projectPath":"<PATH>","action":"soft","ref":"HEAD~1"}
85
+ git-reset (hard): {"projectPath":"<PATH>","action":"hard","ref":"HEAD~1"}
86
+ git-history (log): {"projectPath":"<PATH>","action":"log"}
87
+ git-remote (ensure): {"projectPath":"<PATH>","action":"ensure"}
88
+ git-remote (list): {"projectPath":"<PATH>","action":"list"}
89
+ git-remote (release-create): {"projectPath":"<PATH>","action":"release-create","tag":"v1.0.0","name":"Release v1.0.0"}
90
+ git-worktree (list): {"projectPath":"<PATH>","action":"list"}
91
+ git-worktree (add): {"projectPath":"<PATH>","action":"add","branch":"feature/x"}
92
+ git-sync (pull): {"projectPath":"<PATH>","action":"pull"}
93
+ git-config (get): {"projectPath":"<PATH>","action":"get","key":"user.name"}
94
+ git-ignore (add): {"projectPath":"<PATH>","action":"add","patterns":["node_modules/"]}
95
+ git-files (read): {"projectPath":"<PATH>","action":"read","filepath":"package.json"}
96
+ git-issues (create): {"projectPath":"<PATH>","action":"create","title":"Bug: descrição"}
97
+ git-pulls (create): {"projectPath":"<PATH>","action":"create","head":"feature/x","base":"main","title":"feat: x"}
98
+ git-clone (clone): {"projectPath":"<PARENT_DIR>","action":"clone","url":"https://github.com/user/repo.git"}
99
+ git-help: {"listTools":true} ← única tool sem projectPath/action obrigatórios`
109
100
  }
110
101
  );
111
102
  server.connect(transport);
@@ -530,37 +530,38 @@ Analise os commits e execute os passos para criar a release${assetsInstructions
530
530
  type: "text",
531
531
  text: `# Atualizar Projeto nos Providers
532
532
 
533
- Você precisa atualizar este projeto Git nos providers remotos (GitHub/Gitea).
533
+ Você precisa atualizar o projeto Git atual nos providers remotos (GitHub/Gitea).
534
534
 
535
- ## Status Atual do Repositório
535
+ ## AÇÃO IMEDIATA REQUERIDA
536
536
 
537
- - **É repositório Git?**: ${context.isGitRepo ? "Sim" : "Não"}
538
- - **Branch atual**: ${context.branch || "N/A"}
539
- - **Arquivos modificados**: ${context.modified?.length || 0}
540
- - **Arquivos staged**: ${context.staged?.length || 0}
541
- - **Commits não enviados**: ${context.unpushedCommits || "N/A"}
542
- - **Remotes configurados**: ${context.remotes?.join(", ") || "Nenhum"}
537
+ Chame AGORA a tool \`git-workflow\` com os seguintes parâmetros:
543
538
 
544
- ## Arquivos Modificados
545
- ${context.modified?.map(f => `- ${f}`).join("\n") || "Nenhum"}
539
+ - **projectPath**: caminho absoluto do projeto aberto no IDE (você já sabe qual é — é o projeto que o usuário está trabalhando)
540
+ - **action**: "update"
541
+ - **message**: mensagem de commit descritiva${context.userMessage ? ` — o usuário sugeriu: "${context.userMessage}"` : " — gere uma baseada no contexto"}
546
542
 
547
- ## Instruções
548
-
549
- Execute a atualização completa utilizando uma única chamada com \`git-workflow\` action="update":
543
+ ## Exemplo de chamada
550
544
 
551
545
  \`\`\`json
552
546
  {
553
- "projectPath": "${context.projectPath}",
547
+ "projectPath": "<CAMINHO_ABSOLUTO_DO_PROJETO>",
554
548
  "action": "update",
555
- "message": "tipo: descrição das mudanças",
556
- "gitignore": ["padrao_para_ignorar"], // Opcional
557
- "organization": "nome-da-org" // Opcional: se informado, push vai para a org
549
+ "message": "feat: descrição das mudanças"
558
550
  }
559
551
  \`\`\`
560
552
 
561
- Isso executa automaticamente: status add commit push
553
+ ## Status Coletado${context.projectPath && context.projectPath !== process.cwd() ? ` (projectPath: ${context.projectPath})` : " (projectPath não informado — use o do IDE)"}
554
+
555
+ - **É repositório Git?**: ${context.isGitRepo ? "Sim" : "Não"}
556
+ - **Branch atual**: ${context.branch || "N/A"}
557
+ - **Arquivos modificados**: ${context.modified?.length || 0}
558
+ - **Arquivos staged**: ${context.staged?.length || 0}
559
+ - **Remotes configurados**: ${context.remotes?.join(", ") || "Nenhum"}
560
+
561
+ ## Arquivos Modificados
562
+ ${context.modified?.map(f => `- ${f}`).join("\n") || "Nenhum"}
562
563
 
563
- UTILIZANDO APENAS AS TOOLS DO GIT-MCP.`
564
+ NÃO explique o que vai fazer. Execute diretamente a tool \`git-workflow\` com action="update". UTILIZANDO APENAS AS TOOLS DO GIT-MCP.`
564
565
  }
565
566
  }]
566
567
  }
@@ -836,26 +837,31 @@ async function gatherContext(promptName, args, git, pm) {
836
837
  }
837
838
 
838
839
  case "git-update": {
839
- const isGitRepo = await git.isRepo(projectPath).catch(() => false);
840
- context.isGitRepo = isGitRepo;
841
-
842
- if (isGitRepo) {
843
- const status = await git.status(projectPath).catch(() => ({}));
844
- const branch = await git.getCurrentBranch(projectPath).catch(() => null);
845
- const remotes = await git.listRemotes(projectPath).catch(() => []);
846
-
847
- context.branch = branch;
848
- context.modified = status.modified || [];
849
- context.staged = status.staged || [];
850
- context.remotes = remotes.map(r => r.name || r.remote);
851
-
852
- // Tenta detectar commits não enviados
853
- try {
854
- const localCommits = await git.log(projectPath, { ref: "HEAD", maxCount: 10 }).catch(() => []);
855
- context.unpushedCommits = localCommits.length;
856
- } catch {
857
- context.unpushedCommits = "Desconhecido";
840
+ // Se projectPath não foi fornecido pelo usuário, não tenta coletar status
841
+ // (process.cwd() seria o diretório do servidor MCP, não do projeto do usuário)
842
+ const pathProvidedByUser = !!args.projectPath;
843
+ context.pathProvidedByUser = pathProvidedByUser;
844
+
845
+ if (pathProvidedByUser) {
846
+ const isGitRepo = await git.isRepo(projectPath).catch(() => false);
847
+ context.isGitRepo = isGitRepo;
848
+
849
+ if (isGitRepo) {
850
+ const status = await git.status(projectPath).catch(() => ({}));
851
+ const branch = await git.getCurrentBranch(projectPath).catch(() => null);
852
+ const remotes = await git.listRemotes(projectPath).catch(() => []);
853
+
854
+ context.branch = branch;
855
+ context.modified = status.modified || [];
856
+ context.staged = status.staged || [];
857
+ context.remotes = remotes.map(r => r.name || r.remote);
858
858
  }
859
+ } else {
860
+ // Sem projectPath, não coletamos status — o modelo deve usar o path do IDE
861
+ context.isGitRepo = null;
862
+ context.modified = [];
863
+ context.staged = [];
864
+ context.remotes = [];
859
865
  }
860
866
  break;
861
867
  }
@@ -1,5 +1,5 @@
1
1
  import Ajv from "ajv";
2
- import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
2
+ import { asToolError, asToolResult, errorToResponse, handleEmptyCall } from "../utils/errors.js";
3
3
  import { validateProjectPath } from "../utils/repoHelpers.js";
4
4
 
5
5
  const ajv = new Ajv({ allErrors: true });
@@ -59,8 +59,11 @@ CONVENÇÕES DE NOMES:
59
59
  - release/versao: Para releases`;
60
60
 
61
61
  async function handle(args) {
62
+ const emptyHelp = handleEmptyCall(args, inputSchema, "git-branches", { projectPath: "/path/to/project", action: "list" });
63
+ if (emptyHelp) return emptyHelp;
64
+
62
65
  const validate = ajv.compile(inputSchema);
63
- if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
66
+ if (!validate(args)) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
64
67
  const { projectPath, action } = args;
65
68
  try {
66
69
  validateProjectPath(projectPath);
@@ -1,5 +1,5 @@
1
1
  import Ajv from "ajv";
2
- import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
2
+ import { asToolError, asToolResult, errorToResponse, handleEmptyCall } from "../utils/errors.js";
3
3
  import { validateProjectPath } from "../utils/repoHelpers.js";
4
4
  import { withRetry } from "../utils/retry.js";
5
5
 
@@ -55,8 +55,11 @@ EXEMPLO:
55
55
  - Shallow clone (rápido): action='clone' url='...' depth=1`;
56
56
 
57
57
  async function handle(args) {
58
+ const emptyHelp = handleEmptyCall(args, inputSchema, "git-clone", { projectPath: "/path/to/parent", action: "clone", url: "https://github.com/user/repo.git" });
59
+ if (emptyHelp) return emptyHelp;
60
+
58
61
  const validate = ajv.compile(inputSchema);
59
- if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
62
+ if (!validate(args)) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
60
63
 
61
64
  // projectPath aqui é o diretório PAI
62
65
  const { projectPath, action, url } = args;
@@ -1,5 +1,5 @@
1
1
  import Ajv from "ajv";
2
- import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
2
+ import { asToolError, asToolResult, errorToResponse, handleEmptyCall } from "../utils/errors.js";
3
3
  import { validateProjectPath } from "../utils/repoHelpers.js";
4
4
  import { withRetry } from "../utils/retry.js";
5
5
 
@@ -63,8 +63,11 @@ EXEMPLOS:
63
63
  - Ver nome: action='get' key='user.name'`;
64
64
 
65
65
  async function handle(args) {
66
+ const emptyHelp = handleEmptyCall(args, inputSchema, "git-config", { projectPath: "/path/to/project", action: "get", key: "user.name" });
67
+ if (emptyHelp) return emptyHelp;
68
+
66
69
  const validate = ajv.compile(inputSchema);
67
- if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
70
+ if (!validate(args)) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
68
71
  const { projectPath, action } = args;
69
72
  const scope = args.scope || "local";
70
73
  try {
@@ -1,5 +1,5 @@
1
1
  import Ajv from "ajv";
2
- import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
2
+ import { asToolError, asToolResult, errorToResponse, handleEmptyCall } from "../utils/errors.js";
3
3
  import { validateProjectPath } from "../utils/repoHelpers.js";
4
4
 
5
5
  const ajv = new Ajv({ allErrors: true });
@@ -70,8 +70,11 @@ EXEMPLOS:
70
70
  - Estatísticas de mudança: action='stat' target='HEAD~1'`;
71
71
 
72
72
  async function handle(args) {
73
+ const emptyHelp = handleEmptyCall(args, inputSchema, "git-diff", { projectPath: "/path/to/project", action: "show" });
74
+ if (emptyHelp) return emptyHelp;
75
+
73
76
  const validate = ajv.compile(inputSchema);
74
- if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
77
+ if (!validate(args)) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
75
78
  const { projectPath, action } = args;
76
79
 
77
80
  try {
@@ -1,5 +1,5 @@
1
1
  import Ajv from "ajv";
2
- import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
2
+ import { asToolError, asToolResult, errorToResponse, handleEmptyCall } from "../utils/errors.js";
3
3
  import { validateProjectPath, withRetry } from "../utils/repoHelpers.js";
4
4
 
5
5
  const ajv = new Ajv({ allErrors: true });
@@ -52,8 +52,11 @@ EXEMPLOS:
52
52
  NOTA: Esta tool lê arquivos do histórico Git, não do sistema de arquivos.`;
53
53
 
54
54
  async function handle(args) {
55
+ const emptyHelp = handleEmptyCall(args, inputSchema, "git-files", { projectPath: "/path/to/project", action: "list" });
56
+ if (emptyHelp) return emptyHelp;
57
+
55
58
  const validate = ajv.compile(inputSchema);
56
- if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
59
+ if (!validate(args)) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
57
60
  const { projectPath, action } = args;
58
61
  try {
59
62
  validateProjectPath(projectPath);
@@ -1,5 +1,5 @@
1
1
  import Ajv from "ajv";
2
- import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
2
+ import { asToolError, asToolResult, errorToResponse, handleEmptyCall } from "../utils/errors.js";
3
3
  import { validateProjectPath, withRetry } from "../utils/repoHelpers.js";
4
4
 
5
5
  const ajv = new Ajv({ allErrors: true });
@@ -58,8 +58,11 @@ EXEMPLOS:
58
58
  - Ver histórico de tag: action='log' ref='v1.0.0'`;
59
59
 
60
60
  async function handle(args) {
61
+ const emptyHelp = handleEmptyCall(args, inputSchema, "git-history", { projectPath: "/path/to/project", action: "log" });
62
+ if (emptyHelp) return emptyHelp;
63
+
61
64
  const validate = ajv.compile(inputSchema);
62
- if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
65
+ if (!validate(args)) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
63
66
  const { projectPath, action } = args;
64
67
  try {
65
68
  validateProjectPath(projectPath);
@@ -1,5 +1,5 @@
1
1
  import Ajv from "ajv";
2
- import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
2
+ import { asToolError, asToolResult, errorToResponse, handleEmptyCall } from "../utils/errors.js";
3
3
  import { validateProjectPath } from "../utils/repoHelpers.js";
4
4
  import { withRetry } from "../utils/retry.js";
5
5
 
@@ -59,8 +59,11 @@ EXEMPLOS:
59
59
  - Adicionar padrão: action='add' patterns=['*.tmp']`;
60
60
 
61
61
  async function handle(args) {
62
+ const emptyHelp = handleEmptyCall(args, inputSchema, "git-ignore", { projectPath: "/path/to/project", action: "list" });
63
+ if (emptyHelp) return emptyHelp;
64
+
62
65
  const validate = ajv.compile(inputSchema);
63
- if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
66
+ if (!validate(args)) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
64
67
  const { projectPath, action } = args;
65
68
  const patterns = Array.isArray(args.patterns) ? args.patterns : [];
66
69
  try {
@@ -1,6 +1,6 @@
1
1
  import Ajv from "ajv";
2
2
  import axios from "axios";
3
- import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
3
+ import { asToolError, asToolResult, errorToResponse, handleEmptyCall } from "../utils/errors.js";
4
4
  import { getRepoNameFromPath, validateProjectPath, withRetry } from "../utils/repoHelpers.js";
5
5
  import { runBoth } from "../utils/providerExec.js";
6
6
 
@@ -66,8 +66,11 @@ BOAS PRÁTICAS:
66
66
  - Use labels para categorizar (via git-remote label-create)`;
67
67
 
68
68
  async function handle(args) {
69
+ const emptyHelp = handleEmptyCall(args, inputSchema, "git-issues", { projectPath: "/path/to/project", action: "list" });
70
+ if (emptyHelp) return emptyHelp;
71
+
69
72
  const validate = ajv.compile(inputSchema);
70
- if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
73
+ if (!validate(args)) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
71
74
  validateProjectPath(args.projectPath);
72
75
  const repo = getRepoNameFromPath(args.projectPath);
73
76
  try {
@@ -1,5 +1,5 @@
1
1
  import Ajv from "ajv";
2
- import { asToolError, asToolResult, errorToResponse, createError } from "../utils/errors.js";
2
+ import { asToolError, asToolResult, errorToResponse, createError, handleEmptyCall } from "../utils/errors.js";
3
3
  import { validateProjectPath } from "../utils/repoHelpers.js";
4
4
 
5
5
  const ajv = new Ajv({ allErrors: true });
@@ -68,8 +68,11 @@ SQUASH:
68
68
  - Útil para manter histórico limpo`;
69
69
 
70
70
  async function handle(args) {
71
+ const emptyHelp = handleEmptyCall(args, inputSchema, "git-merge", { projectPath: "/path/to/project", action: "status" });
72
+ if (emptyHelp) return emptyHelp;
73
+
71
74
  const validate = ajv.compile(inputSchema);
72
- if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
75
+ if (!validate(args)) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
73
76
  const { projectPath, action } = args;
74
77
 
75
78
  try {
@@ -1,6 +1,6 @@
1
1
  import Ajv from "ajv";
2
2
  import axios from "axios";
3
- import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
3
+ import { asToolError, asToolResult, errorToResponse, handleEmptyCall } from "../utils/errors.js";
4
4
  import { getRepoNameFromPath, validateProjectPath, withRetry } from "../utils/repoHelpers.js";
5
5
  import { runBoth } from "../utils/providerExec.js";
6
6
 
@@ -76,8 +76,11 @@ AÇÕES:
76
76
  NOTA: O PR é criado em AMBOS os providers simultaneamente.`;
77
77
 
78
78
  async function handle(args) {
79
+ const emptyHelp = handleEmptyCall(args, inputSchema, "git-pulls", { projectPath: "/path/to/project", action: "list" });
80
+ if (emptyHelp) return emptyHelp;
81
+
79
82
  const validate = ajv.compile(inputSchema);
80
- if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
83
+ if (!validate(args)) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
81
84
  validateProjectPath(args.projectPath);
82
85
  const repo = getRepoNameFromPath(args.projectPath);
83
86
  try {
@@ -3,7 +3,7 @@ import axios from "axios";
3
3
  import fs from "node:fs";
4
4
  import path from "node:path";
5
5
  import archiver from "archiver";
6
- import { asToolError, asToolResult, errorToResponse, mapExternalError } from "../utils/errors.js";
6
+ import { asToolError, asToolResult, errorToResponse, mapExternalError, handleEmptyCall } from "../utils/errors.js";
7
7
  import { getRepoNameFromPath, validateProjectPath, withRetry } from "../utils/repoHelpers.js";
8
8
  import { runBoth } from "../utils/providerExec.js";
9
9
  import { sendLog, requestConfirmation } from "../utils/mcpNotify.js";
@@ -218,8 +218,11 @@ QUANDO USAR:
218
218
  - Para configurar tópicos: use action='topics-set'`;
219
219
 
220
220
  async function handle(args) {
221
+ const emptyHelp = handleEmptyCall(args, inputSchema, "git-remote", { projectPath: "/path/to/project", action: "list" });
222
+ if (emptyHelp) return emptyHelp;
223
+
221
224
  const validate = ajv.compile(inputSchema);
222
- if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
225
+ if (!validate(args)) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
223
226
  const { projectPath, action } = args;
224
227
  try {
225
228
  validateProjectPath(projectPath);
@@ -1,5 +1,5 @@
1
1
  import Ajv from "ajv";
2
- import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
2
+ import { asToolError, asToolResult, errorToResponse, handleEmptyCall } from "../utils/errors.js";
3
3
  import { validateProjectPath, withRetry } from "../utils/repoHelpers.js";
4
4
  import { sendLog, requestConfirmation } from "../utils/mcpNotify.js";
5
5
 
@@ -57,8 +57,11 @@ REFERÊNCIAS:
57
57
  - abc1234: SHA específico do commit`;
58
58
 
59
59
  async function handle(args) {
60
+ const emptyHelp = handleEmptyCall(args, inputSchema, "git-reset", { projectPath: "/path/to/project", action: "soft", ref: "HEAD~1" });
61
+ if (emptyHelp) return emptyHelp;
62
+
60
63
  const validate = ajv.compile(inputSchema);
61
- if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
64
+ if (!validate(args)) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
62
65
  const { projectPath, action, ref } = args;
63
66
  try {
64
67
  validateProjectPath(projectPath);
@@ -1,5 +1,5 @@
1
1
  import Ajv from "ajv";
2
- import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
2
+ import { asToolError, asToolResult, errorToResponse, handleEmptyCall } from "../utils/errors.js";
3
3
  import { validateProjectPath } from "../utils/repoHelpers.js";
4
4
  import { withRetry } from "../utils/retry.js";
5
5
 
@@ -67,8 +67,11 @@ AÇÕES:
67
67
  - clear: Remover todos os stashes`;
68
68
 
69
69
  async function handle(args) {
70
+ const emptyHelp = handleEmptyCall(args, inputSchema, "git-stash", { projectPath: "/path/to/project", action: "list" });
71
+ if (emptyHelp) return emptyHelp;
72
+
70
73
  const validate = ajv.compile(inputSchema);
71
- if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
74
+ if (!validate(args)) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
72
75
  const { projectPath, action } = args;
73
76
  try {
74
77
  validateProjectPath(projectPath);
@@ -1,5 +1,5 @@
1
1
  import Ajv from "ajv";
2
- import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
2
+ import { asToolError, asToolResult, errorToResponse, handleEmptyCall } from "../utils/errors.js";
3
3
  import { validateProjectPath, withRetry } from "../utils/repoHelpers.js";
4
4
 
5
5
  const ajv = new Ajv({ allErrors: true });
@@ -54,8 +54,11 @@ FLUXO RECOMENDADO:
54
54
  NOTA: Se pull falhar com conflito, resolva manualmente e faça commit.`;
55
55
 
56
56
  async function handle(args) {
57
+ const emptyHelp = handleEmptyCall(args, inputSchema, "git-sync", { projectPath: "/path/to/project", action: "fetch" });
58
+ if (emptyHelp) return emptyHelp;
59
+
57
60
  const validate = ajv.compile(inputSchema);
58
- if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
61
+ if (!validate(args)) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
59
62
  const { projectPath, action } = args;
60
63
  try {
61
64
  validateProjectPath(projectPath);
@@ -1,5 +1,5 @@
1
1
  import Ajv from "ajv";
2
- import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
2
+ import { asToolError, asToolResult, errorToResponse, handleEmptyCall } from "../utils/errors.js";
3
3
  import { validateProjectPath, withRetry } from "../utils/repoHelpers.js";
4
4
 
5
5
  const ajv = new Ajv({ allErrors: true });
@@ -61,8 +61,11 @@ FLUXO TÍPICO:
61
61
  2. git-tags push tag='v1.0.0'`;
62
62
 
63
63
  async function handle(args) {
64
+ const emptyHelp = handleEmptyCall(args, inputSchema, "git-tags", { projectPath: "/path/to/project", action: "list" });
65
+ if (emptyHelp) return emptyHelp;
66
+
64
67
  const validate = ajv.compile(inputSchema);
65
- if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
68
+ if (!validate(args)) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
66
69
  const { projectPath, action } = args;
67
70
  try {
68
71
  validateProjectPath(projectPath);
@@ -1,7 +1,7 @@
1
1
  import Ajv from "ajv";
2
2
  import fs from "fs";
3
3
  import path from "path";
4
- import { asToolError, asToolResult, errorToResponse, createError } from "../utils/errors.js";
4
+ import { asToolError, asToolResult, errorToResponse, createError, handleEmptyCall } from "../utils/errors.js";
5
5
  import { getRepoNameFromPath, detectProjectType, GITIGNORE_TEMPLATES, validateProjectPath, withRetry } from "../utils/repoHelpers.js";
6
6
  import { resolveWorktreeContext, isProtectedPath } from "../utils/worktreeResolver.js";
7
7
  import { sendLog } from "../utils/mcpNotify.js";
@@ -134,8 +134,12 @@ EXEMPLOS DE USO:
134
134
  • Ver mudanças: { "projectPath": "/path/to/project", "action": "status" }`;
135
135
 
136
136
  async function handle(args) {
137
+ // Call vazia = modelo tentando descobrir o schema. Retorna help em vez de erro.
138
+ const emptyHelp = handleEmptyCall(args, inputSchema, "git-workflow", { projectPath: "/path/to/project", action: "update", message: "feat: descrição" });
139
+ if (emptyHelp) return emptyHelp;
140
+
137
141
  const validate = ajv.compile(inputSchema);
138
- if (!validate(args || {})) {
142
+ if (!validate(args)) {
139
143
  return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
140
144
  }
141
145
  const { projectPath, action } = args;
@@ -1,7 +1,7 @@
1
1
  import Ajv from "ajv";
2
2
  import fs from "fs";
3
3
  import path from "path";
4
- import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
4
+ import { asToolError, asToolResult, errorToResponse, handleEmptyCall } from "../utils/errors.js";
5
5
  import { validateProjectPath, withRetry } from "../utils/repoHelpers.js";
6
6
  import { resolveWorktreeContext, isProtectedPath } from "../utils/worktreeResolver.js";
7
7
 
@@ -73,8 +73,11 @@ AÇÕES:
73
73
  - set-channel: Configurar canal de deploy (prod/beta/alpha)`;
74
74
 
75
75
  async function handle(args) {
76
+ const emptyHelp = handleEmptyCall(args, inputSchema, "git-worktree", { projectPath: "/path/to/project", action: "list" });
77
+ if (emptyHelp) return emptyHelp;
78
+
76
79
  const validate = ajv.compile(inputSchema);
77
- if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
80
+ if (!validate(args)) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
78
81
  const { projectPath, action } = args;
79
82
  let effectivePath = projectPath;
80
83
  let worktreeCtx = null;
@@ -432,3 +432,24 @@ export function errorToResponse(error) {
432
432
  const mapped = mapExternalError(error);
433
433
  return asToolError(mapped.code, mapped.message, mapped.data);
434
434
  }
435
+
436
+ /**
437
+ * Detecta call vazia (sem projectPath/action) e retorna help em vez de erro seco.
438
+ * Isso evita que modelos façam "tool probing" — uma call vazia para descobrir o schema.
439
+ * @param {object} args - Argumentos recebidos
440
+ * @param {object} inputSchema - Schema da tool
441
+ * @param {string} toolName - Nome da tool
442
+ * @param {object} example - Exemplo de uso mínimo
443
+ * @returns {object|null} - Resposta de help, ou null se args não estão vazios
444
+ */
445
+ export function handleEmptyCall(args, inputSchema, toolName, example) {
446
+ if (args && Object.keys(args).length > 0) return null;
447
+ return asToolResult({
448
+ help: true,
449
+ tool: toolName,
450
+ requiredParams: (inputSchema.required || []).join(", "),
451
+ example,
452
+ actions: inputSchema.properties?.action?.enum || [],
453
+ hint: `Sempre inclua ${(inputSchema.required || []).join(" e ")} na chamada.`
454
+ });
455
+ }