@andre.buzeli/git-mcp 15.12.5

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.
@@ -0,0 +1,126 @@
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 createGitBranchesTool(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: ["list", "create", "delete", "rename", "checkout"],
18
+ description: `Ação a executar:
19
+ - list: Lista todas as branches locais e remotas
20
+ - create: Cria nova branch a partir do HEAD atual
21
+ - delete: Remove uma branch (não pode ser a atual)
22
+ - rename: Renomeia uma branch existente
23
+ - checkout: Muda para outra branch`
24
+ },
25
+ branch: {
26
+ type: "string",
27
+ description: "Nome da branch para create/delete/checkout. Ex: 'feature/nova-funcionalidade', 'bugfix/correcao'"
28
+ },
29
+ newBranch: {
30
+ type: "string",
31
+ description: "Novo nome da branch (apenas para action='rename')"
32
+ },
33
+ force: {
34
+ type: "boolean",
35
+ description: "Forçar delete mesmo se branch não está merged. Default: false"
36
+ }
37
+ },
38
+ required: ["projectPath", "action"],
39
+ additionalProperties: false
40
+ };
41
+
42
+ const description = `Gerenciamento de branches Git.
43
+
44
+ AÇÕES DISPONÍVEIS:
45
+ - list: Ver branches existentes (locais e remotas)
46
+ - create: Criar nova branch para desenvolvimento
47
+ - checkout: Trocar de branch
48
+ - delete: Remover branch após merge
49
+ - rename: Renomear branch
50
+
51
+ CONVENÇÕES DE NOMES:
52
+ - feature/nome: Para novas funcionalidades
53
+ - bugfix/nome: Para correções de bugs
54
+ - hotfix/nome: Para correções urgentes
55
+ - release/versao: Para releases`;
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
+ try {
62
+ validateProjectPath(projectPath);
63
+ if (action === "list") {
64
+ const local = await git.listBranches(projectPath, false);
65
+ const remote = await git.listBranches(projectPath, true).catch(() => []);
66
+ const current = await git.getCurrentBranch(projectPath);
67
+ return asToolResult({ current, local, remote });
68
+ }
69
+ if (action === "create") {
70
+ if (!args.branch) return asToolError("MISSING_PARAMETER", "branch é obrigatório para criar", { parameter: "branch" });
71
+ const existing = await git.listBranches(projectPath, false);
72
+ if (existing.includes(args.branch)) {
73
+ return asToolError("BRANCH_ALREADY_EXISTS", `Branch '${args.branch}' já existe`, { branch: args.branch, existingBranches: existing });
74
+ }
75
+ await git.createBranch(projectPath, args.branch);
76
+ return asToolResult({ success: true, branch: args.branch, message: `Branch '${args.branch}' criada. Use action='checkout' para mudar para ela.` });
77
+ }
78
+ if (action === "delete") {
79
+ if (!args.branch) return asToolError("MISSING_PARAMETER", "branch é obrigatório para deletar", { parameter: "branch" });
80
+ const existing = await git.listBranches(projectPath, false);
81
+ if (!existing.includes(args.branch)) {
82
+ return asToolError("BRANCH_NOT_FOUND", `Branch '${args.branch}' não encontrada`, { branch: args.branch, availableBranches: existing });
83
+ }
84
+ const current = await git.getCurrentBranch(projectPath);
85
+ if (args.branch === current) {
86
+ return asToolError("CANNOT_DELETE_CURRENT", `Não pode deletar branch atual '${args.branch}'`, { branch: args.branch, suggestion: "Faça checkout para outra branch primeiro" });
87
+ }
88
+ await git.deleteBranch(projectPath, args.branch, !!args.force);
89
+ return asToolResult({ success: true, branch: args.branch, deleted: true });
90
+ }
91
+ if (action === "rename") {
92
+ if (!args.branch || !args.newBranch) return asToolError("MISSING_PARAMETER", "branch e newBranch são obrigatórios", { parameters: ["branch", "newBranch"] });
93
+ await git.renameBranch(projectPath, args.branch, args.newBranch);
94
+ return asToolResult({ success: true, from: args.branch, to: args.newBranch });
95
+ }
96
+ if (action === "checkout") {
97
+ if (!args.branch) return asToolError("MISSING_PARAMETER", "branch é obrigatório para checkout", { parameter: "branch" });
98
+
99
+ try {
100
+ await git.checkout(projectPath, args.branch);
101
+ } catch (e) {
102
+ const msg = e.message || "";
103
+ // Auto-fix: Create if not exists (common intent)
104
+ if (msg.includes("did not match any file") || msg.includes("pathspec") || msg.includes("not found")) {
105
+ // We can suggest or auto-create if configured. For now, let's suggest clearly or auto-create if it looks like a feature branch?
106
+ // Safer: Suggest. Or better: check if we should auto-create.
107
+ // Let's stick to safe behavior but enhanced error.
108
+
109
+ // Check if user meant create
110
+ return asToolError("BRANCH_NOT_FOUND", `Branch '${args.branch}' não existe`, {
111
+ suggestion: "Use action='create' para criar esta branch, ou verifique o nome."
112
+ });
113
+ }
114
+ throw e;
115
+ }
116
+
117
+ return asToolResult({ success: true, branch: args.branch, message: `Mudou para branch '${args.branch}'` });
118
+ }
119
+ return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, { availableActions: ["list", "create", "delete", "rename", "checkout"] });
120
+ } catch (e) {
121
+ return errorToResponse(e);
122
+ }
123
+ }
124
+
125
+ return { name: "git-branches", description, inputSchema, handle };
126
+ }
@@ -0,0 +1,137 @@
1
+ import Ajv from "ajv";
2
+ import { asToolError, asToolResult, errorToResponse, createError } from "../utils/errors.js";
3
+ import { validateProjectPath, withRetry } from "../utils/repoHelpers.js";
4
+
5
+ const ajv = new Ajv({ allErrors: true });
6
+
7
+ export function createGitCloneTool(git) {
8
+ const inputSchema = {
9
+ type: "object",
10
+ properties: {
11
+ projectPath: {
12
+ type: "string",
13
+ description: "Caminho absoluto onde o repositório será clonado"
14
+ },
15
+ action: {
16
+ type: "string",
17
+ enum: ["clone"],
18
+ description: "Ação a executar: clone - clona um repositório remoto"
19
+ },
20
+ url: {
21
+ type: "string",
22
+ description: "URL do repositório para clonar. Ex: 'https://github.com/user/repo.git'"
23
+ },
24
+ branch: {
25
+ type: "string",
26
+ description: "Branch específica para clonar. Default: branch padrão do repo"
27
+ },
28
+ depth: {
29
+ 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
+ }
36
+ },
37
+ required: ["projectPath", "action", "url"],
38
+ additionalProperties: false
39
+ };
40
+
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
47
+
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'
52
+
53
+ AUTENTICAÇÃO:
54
+ - Repos públicos: Não precisa de token
55
+ - Repos privados: Configure GITHUB_TOKEN ou GITEA_TOKEN
56
+
57
+ NOTAS:
58
+ - O diretório de destino será criado automaticamente
59
+ - Se já existir arquivos, o clone falhará`;
60
+
61
+ async function handle(args) {
62
+ const validate = ajv.compile(inputSchema);
63
+ if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
64
+ const { projectPath, action, url } = args;
65
+
66
+ try {
67
+ // Validação de path
68
+ validateProjectPath(projectPath);
69
+
70
+ 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
+ }
77
+
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
+ );
116
+
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"
125
+ });
126
+ }
127
+
128
+ return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, {
129
+ availableActions: ["clone"]
130
+ });
131
+ } catch (e) {
132
+ return errorToResponse(e);
133
+ }
134
+ }
135
+
136
+ return { name: "git-clone", description, inputSchema, handle };
137
+ }
@@ -0,0 +1,94 @@
1
+ import Ajv from "ajv";
2
+ import { asToolError, asToolResult, errorToResponse } from "../utils/errors.js";
3
+ import { validateProjectPath } from "../utils/repoHelpers.js";
4
+ import { withRetry } from "../utils/retry.js";
5
+
6
+ const ajv = new Ajv({ allErrors: true });
7
+
8
+ export function createGitConfigTool(git) {
9
+ const inputSchema = {
10
+ type: "object",
11
+ properties: {
12
+ projectPath: {
13
+ type: "string",
14
+ description: "Caminho absoluto do diretório do projeto"
15
+ },
16
+ action: {
17
+ type: "string",
18
+ enum: ["get", "set", "unset", "list"],
19
+ description: `Ação a executar:
20
+ - get: Obter valor de uma configuração específica
21
+ - set: Definir valor de uma configuração
22
+ - unset: Remover uma configuração
23
+ - list: Listar todas as configurações do escopo`
24
+ },
25
+ key: {
26
+ type: "string",
27
+ description: "Chave da configuração. Ex: 'user.name', 'user.email', 'core.autocrlf'"
28
+ },
29
+ value: {
30
+ type: "string",
31
+ description: "Valor a definir (apenas para action='set')"
32
+ },
33
+ scope: {
34
+ type: "string",
35
+ enum: ["local", "global", "system"],
36
+ description: "Escopo da configuração. local: apenas este repo, global: todos os repos do usuário, system: todo o sistema"
37
+ }
38
+ },
39
+ required: ["projectPath", "action"],
40
+ additionalProperties: false
41
+ };
42
+
43
+ const description = `Gerenciamento de configurações Git.
44
+
45
+ CONFIGURAÇÕES COMUNS:
46
+ - user.name: Nome do autor dos commits
47
+ - user.email: Email do autor dos commits
48
+ - core.autocrlf: Conversão de fim de linha (true/false/input)
49
+ - core.editor: Editor padrão para commits
50
+
51
+ ESCOPOS:
52
+ - local: Apenas neste repositório (.git/config)
53
+ - global: Todos os repos do usuário (~/.gitconfig)
54
+ - system: Todo o sistema (/etc/gitconfig)
55
+
56
+ EXEMPLOS:
57
+ - Definir nome: action='set' key='user.name' value='Meu Nome'
58
+ - Definir email: action='set' key='user.email' value='email@example.com'
59
+ - Ver nome: action='get' key='user.name'`;
60
+
61
+ async function handle(args) {
62
+ const validate = ajv.compile(inputSchema);
63
+ if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
64
+ const { projectPath, action } = args;
65
+ const scope = args.scope || "local";
66
+ try {
67
+ validateProjectPath(projectPath);
68
+ if (action === "get") {
69
+ if (!args.key) return asToolError("MISSING_PARAMETER", "key é obrigatório", { parameter: "key" });
70
+ const val = await withRetry(() => git.getConfig(projectPath, args.key, scope), 3, "get-config");
71
+ return asToolResult({ key: args.key, value: val, found: val !== undefined, scope });
72
+ }
73
+ if (action === "set") {
74
+ if (!args.key) return asToolError("MISSING_PARAMETER", "key é obrigatório", { parameter: "key" });
75
+ await withRetry(() => git.setConfig(projectPath, args.key, args.value ?? "", scope), 3, "set-config");
76
+ return asToolResult({ success: true, key: args.key, value: args.value ?? "", scope });
77
+ }
78
+ if (action === "unset") {
79
+ if (!args.key) return asToolError("MISSING_PARAMETER", "key é obrigatório", { parameter: "key" });
80
+ await withRetry(() => git.unsetConfig(projectPath, args.key, scope), 3, "unset-config");
81
+ return asToolResult({ success: true, key: args.key, removed: true, scope });
82
+ }
83
+ if (action === "list") {
84
+ const items = await withRetry(() => git.listConfig(projectPath, scope), 3, "list-config");
85
+ return asToolResult({ scope, configs: items, count: Object.keys(items).length });
86
+ }
87
+ return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, { availableActions: ["get", "set", "unset", "list"] });
88
+ } catch (e) {
89
+ return errorToResponse(e);
90
+ }
91
+ }
92
+
93
+ return { name: "git-config", description, inputSchema, handle };
94
+ }
@@ -0,0 +1,137 @@
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
+ }
@@ -0,0 +1,82 @@
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 createGitFilesTool(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: ["list", "read"],
18
+ description: `Ação a executar:
19
+ - list: Lista todos os arquivos rastreados pelo Git em um commit/branch
20
+ - read: Lê o conteúdo de um arquivo em um commit/branch específico`
21
+ },
22
+ filepath: {
23
+ type: "string",
24
+ description: "Caminho do arquivo relativo à raiz do repo (action='read'). Ex: 'src/index.js', 'package.json'"
25
+ },
26
+ ref: {
27
+ type: "string",
28
+ description: "Referência Git para ler (commit SHA, branch, tag). Default: HEAD (commit atual)"
29
+ }
30
+ },
31
+ required: ["projectPath", "action"],
32
+ additionalProperties: false
33
+ };
34
+
35
+ const description = `Leitura de arquivos do repositório Git.
36
+
37
+ QUANDO USAR:
38
+ - Ver arquivos de um commit antigo
39
+ - Comparar versões de arquivo entre commits
40
+ - Listar arquivos rastreados pelo Git
41
+
42
+ EXEMPLOS:
43
+ - Listar arquivos: action='list'
44
+ - Listar arquivos de tag: action='list' ref='v1.0.0'
45
+ - Ler arquivo atual: action='read' filepath='package.json'
46
+ - Ler arquivo de commit antigo: action='read' filepath='src/index.js' ref='abc1234'
47
+
48
+ NOTA: Esta tool lê arquivos do histórico Git, não do sistema de arquivos.`;
49
+
50
+ async function handle(args) {
51
+ const validate = ajv.compile(inputSchema);
52
+ if (!validate(args || {})) return asToolError("VALIDATION_ERROR", "Parâmetros inválidos", validate.errors);
53
+ const { projectPath, action } = args;
54
+ try {
55
+ validateProjectPath(projectPath);
56
+ if (action === "list") {
57
+ const files = await git.listFiles(projectPath, args.ref || "HEAD");
58
+ return asToolResult({ files, count: files.length, ref: args.ref || "HEAD" });
59
+ }
60
+ if (action === "read") {
61
+ if (!args.filepath) return asToolError("MISSING_PARAMETER", "filepath é obrigatório", { parameter: "filepath" });
62
+ try {
63
+ const text = await git.readFile(projectPath, args.filepath, args.ref || "HEAD");
64
+ return { content: [{ type: "text", text }], isError: false };
65
+ } catch (e) {
66
+ const files = await git.listFiles(projectPath, args.ref || "HEAD").catch(() => []);
67
+ return asToolError("FILE_NOT_FOUND", `Arquivo '${args.filepath}' não encontrado`, {
68
+ filepath: args.filepath,
69
+ ref: args.ref || "HEAD",
70
+ similarFiles: files.filter(f => f.includes(args.filepath.split("/").pop())).slice(0, 10),
71
+ totalFiles: files.length
72
+ });
73
+ }
74
+ }
75
+ return asToolError("VALIDATION_ERROR", `Ação '${action}' não suportada`, { availableActions: ["list", "read"] });
76
+ } catch (e) {
77
+ return errorToResponse(e);
78
+ }
79
+ }
80
+
81
+ return { name: "git-files", description, inputSchema, handle };
82
+ }