@andrebuzeli/git-mcp 10.0.3 → 10.0.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.
- package/README.md +37 -56
- package/dist/index.js +50 -13
- package/dist/server-new.d.ts +2 -0
- package/dist/server-new.js +224 -0
- package/dist/tools/gitArchive.d.ts +0 -8
- package/dist/tools/gitArchive.js +4 -12
- package/dist/tools/gitBackup.js +7 -7
- package/dist/tools/gitFiles-new.d.ts +89 -0
- package/dist/tools/gitFiles-new.js +335 -0
- package/dist/tools/gitFiles.d.ts +18 -15
- package/dist/tools/gitFiles.js +54 -15
- package/dist/tools/gitFix.js +7 -7
- package/dist/tools/gitFix.tool.js +4 -4
- package/dist/tools/gitHistory.d.ts +2 -25
- package/dist/tools/gitHistory.js +4 -25
- package/dist/tools/gitIgnore.d.ts +2 -14
- package/dist/tools/gitIgnore.js +5 -17
- package/dist/tools/gitIssues.d.ts +0 -4
- package/dist/tools/gitIssues.js +12 -18
- package/dist/tools/gitMonitor.js +1 -1
- package/dist/tools/gitPackages.d.ts +28 -0
- package/dist/tools/gitPackages.js +29 -1
- package/dist/tools/gitPulls.d.ts +60 -0
- package/dist/tools/gitPulls.js +68 -4
- package/dist/tools/gitRelease.d.ts +43 -0
- package/dist/tools/gitRelease.js +48 -4
- package/dist/tools/gitRemote.d.ts +23 -0
- package/dist/tools/gitRemote.js +23 -0
- package/dist/tools/gitReset.d.ts +23 -0
- package/dist/tools/gitReset.js +23 -0
- package/dist/tools/gitStash.d.ts +31 -0
- package/dist/tools/gitStash.js +31 -0
- package/dist/tools/gitSync.d.ts +6 -2
- package/dist/tools/gitSync.js +10 -6
- package/dist/tools/gitTags.d.ts +31 -0
- package/dist/tools/gitTags.js +31 -0
- package/dist/tools/gitUpdate.d.ts +0 -27
- package/dist/tools/gitUpdate.js +1 -26
- package/dist/tools/gitUpload.d.ts +2 -9
- package/dist/tools/gitUpload.js +81 -42
- package/dist/tools/gitWorkflow.d.ts +8 -0
- package/dist/tools/gitWorkflow.js +37 -3
- package/dist/utils/cache.d.ts +96 -0
- package/dist/utils/cache.js +208 -0
- package/dist/utils/gitAdapter.js +19 -3
- package/dist/utils/logger.d.ts +45 -0
- package/dist/utils/logger.js +140 -0
- package/dist/utils/rateLimiter.d.ts +113 -0
- package/dist/utils/rateLimiter.js +257 -0
- package/dist/utils/validation.d.ts +115 -0
- package/dist/utils/validation.js +270 -0
- package/package.json +1 -1
- package/dist/config.d.ts +0 -5
- package/dist/config.js +0 -35
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { Tool } from '../types.js';
|
|
2
|
+
export declare class GitFilesTool implements Tool {
|
|
3
|
+
name: string;
|
|
4
|
+
description: string;
|
|
5
|
+
inputSchema: {
|
|
6
|
+
type: "object";
|
|
7
|
+
properties: {
|
|
8
|
+
action: {
|
|
9
|
+
type: string;
|
|
10
|
+
enum: string[];
|
|
11
|
+
description: string;
|
|
12
|
+
};
|
|
13
|
+
projectPath: {
|
|
14
|
+
type: string;
|
|
15
|
+
description: string;
|
|
16
|
+
};
|
|
17
|
+
filePath: {
|
|
18
|
+
type: string;
|
|
19
|
+
description: string;
|
|
20
|
+
};
|
|
21
|
+
directoryPath: {
|
|
22
|
+
type: string;
|
|
23
|
+
description: string;
|
|
24
|
+
};
|
|
25
|
+
content: {
|
|
26
|
+
type: string;
|
|
27
|
+
description: string;
|
|
28
|
+
};
|
|
29
|
+
comment: {
|
|
30
|
+
type: string;
|
|
31
|
+
description: string;
|
|
32
|
+
};
|
|
33
|
+
patterns: {
|
|
34
|
+
type: string;
|
|
35
|
+
items: {
|
|
36
|
+
type: string;
|
|
37
|
+
};
|
|
38
|
+
description: string;
|
|
39
|
+
};
|
|
40
|
+
searchText: {
|
|
41
|
+
type: string;
|
|
42
|
+
description: string;
|
|
43
|
+
};
|
|
44
|
+
query: {
|
|
45
|
+
type: string;
|
|
46
|
+
description: string;
|
|
47
|
+
};
|
|
48
|
+
searchPath: {
|
|
49
|
+
type: string;
|
|
50
|
+
description: string;
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
required: string[];
|
|
54
|
+
additionalProperties: boolean;
|
|
55
|
+
};
|
|
56
|
+
handle(params: Record<string, any>): Promise<any>;
|
|
57
|
+
/**
|
|
58
|
+
* Lê arquivo com cache
|
|
59
|
+
*/
|
|
60
|
+
private handleRead;
|
|
61
|
+
/**
|
|
62
|
+
* Lista diretório com cache
|
|
63
|
+
*/
|
|
64
|
+
private handleList;
|
|
65
|
+
/**
|
|
66
|
+
* Cria arquivo com validação de segurança
|
|
67
|
+
*/
|
|
68
|
+
private handleCreate;
|
|
69
|
+
/**
|
|
70
|
+
* Atualiza arquivo com validação de segurança
|
|
71
|
+
*/
|
|
72
|
+
private handleUpdate;
|
|
73
|
+
/**
|
|
74
|
+
* Deleta arquivo com validação de segurança
|
|
75
|
+
*/
|
|
76
|
+
private handleDelete;
|
|
77
|
+
/**
|
|
78
|
+
* Busca texto em arquivos com cache
|
|
79
|
+
*/
|
|
80
|
+
private handleSearch;
|
|
81
|
+
/**
|
|
82
|
+
* Busca recursivamente em diretórios
|
|
83
|
+
*/
|
|
84
|
+
private searchInDirectory;
|
|
85
|
+
/**
|
|
86
|
+
* Verifica se arquivo é buscável
|
|
87
|
+
*/
|
|
88
|
+
private isSearchableFile;
|
|
89
|
+
}
|
|
@@ -0,0 +1,335 @@
|
|
|
1
|
+
import fs from 'fs/promises';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import { MCPError } from '../utils/errors.js';
|
|
4
|
+
import { cacheHelpers } from '../utils/cache.js';
|
|
5
|
+
import { Logger } from '../utils/logger.js';
|
|
6
|
+
export class GitFilesTool {
|
|
7
|
+
constructor() {
|
|
8
|
+
this.name = 'git-files';
|
|
9
|
+
this.description = 'Read-only file operations for repositories - local operations only with caching and rate limiting';
|
|
10
|
+
this.inputSchema = {
|
|
11
|
+
type: "object",
|
|
12
|
+
properties: {
|
|
13
|
+
action: {
|
|
14
|
+
type: "string",
|
|
15
|
+
enum: ["read", "list", "create", "update", "delete", "search"],
|
|
16
|
+
description: "File operation to perform"
|
|
17
|
+
},
|
|
18
|
+
projectPath: {
|
|
19
|
+
type: "string",
|
|
20
|
+
description: "Absolute path to the project directory (REQUIRED)"
|
|
21
|
+
},
|
|
22
|
+
filePath: {
|
|
23
|
+
type: "string",
|
|
24
|
+
description: "Relative path to the file within projectPath - required for read, create, update, delete"
|
|
25
|
+
},
|
|
26
|
+
directoryPath: {
|
|
27
|
+
type: "string",
|
|
28
|
+
description: "Relative path to directory for listing - optional, defaults to projectPath root"
|
|
29
|
+
},
|
|
30
|
+
content: {
|
|
31
|
+
type: "string",
|
|
32
|
+
description: "File content to write - required for create and update actions"
|
|
33
|
+
},
|
|
34
|
+
comment: {
|
|
35
|
+
type: "string",
|
|
36
|
+
description: "Comment to add when appending patterns to .gitignore - optional for add action"
|
|
37
|
+
},
|
|
38
|
+
patterns: {
|
|
39
|
+
type: "array",
|
|
40
|
+
items: { type: "string" },
|
|
41
|
+
description: "Array of file patterns - required for add and remove actions"
|
|
42
|
+
},
|
|
43
|
+
searchText: {
|
|
44
|
+
type: "string",
|
|
45
|
+
description: "Text to search for in files - required for search action"
|
|
46
|
+
},
|
|
47
|
+
query: {
|
|
48
|
+
type: "string",
|
|
49
|
+
description: "Alternative name for searchText - required for search action"
|
|
50
|
+
},
|
|
51
|
+
searchPath: {
|
|
52
|
+
type: "string",
|
|
53
|
+
description: "Relative path to search within - optional for search, defaults to projectPath root"
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
required: ["projectPath", "action"],
|
|
57
|
+
additionalProperties: true
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
async handle(params) {
|
|
61
|
+
const logger = Logger.getInstance();
|
|
62
|
+
const startTime = Date.now();
|
|
63
|
+
try {
|
|
64
|
+
const action = params.action;
|
|
65
|
+
const projectPath = params.projectPath;
|
|
66
|
+
if (!action || !projectPath) {
|
|
67
|
+
throw new MCPError('VALIDATION_ERROR', 'action and projectPath are required');
|
|
68
|
+
}
|
|
69
|
+
logger.debug(`Executing git-files:${action}`, { projectPath, filePath: params.filePath });
|
|
70
|
+
let result;
|
|
71
|
+
switch (action) {
|
|
72
|
+
case 'read':
|
|
73
|
+
result = await this.handleRead(projectPath, params.filePath);
|
|
74
|
+
break;
|
|
75
|
+
case 'list':
|
|
76
|
+
result = await this.handleList(projectPath, params.directoryPath);
|
|
77
|
+
break;
|
|
78
|
+
case 'create':
|
|
79
|
+
result = await this.handleCreate(projectPath, params.filePath, params.content || '');
|
|
80
|
+
// Invalidar cache após modificação
|
|
81
|
+
cacheHelpers.invalidateRepoCache(projectPath);
|
|
82
|
+
break;
|
|
83
|
+
case 'update':
|
|
84
|
+
result = await this.handleUpdate(projectPath, params.filePath, params.content);
|
|
85
|
+
// Invalidar cache após modificação
|
|
86
|
+
cacheHelpers.invalidateRepoCache(projectPath);
|
|
87
|
+
break;
|
|
88
|
+
case 'delete':
|
|
89
|
+
result = await this.handleDelete(projectPath, params.filePath);
|
|
90
|
+
// Invalidar cache após modificação
|
|
91
|
+
cacheHelpers.invalidateRepoCache(projectPath);
|
|
92
|
+
break;
|
|
93
|
+
case 'search':
|
|
94
|
+
result = await this.handleSearch(projectPath, params.searchText || params.query, params.searchPath);
|
|
95
|
+
break;
|
|
96
|
+
default:
|
|
97
|
+
throw new MCPError('VALIDATION_ERROR', `Unknown action: ${action}`);
|
|
98
|
+
}
|
|
99
|
+
const duration = Date.now() - startTime;
|
|
100
|
+
logger.debug(`git-files:${action} completed in ${duration}ms`);
|
|
101
|
+
return result;
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
const duration = Date.now() - startTime;
|
|
105
|
+
logger.error(`git-files operation failed`, {
|
|
106
|
+
action: params.action,
|
|
107
|
+
projectPath: params.projectPath,
|
|
108
|
+
duration,
|
|
109
|
+
error: error.message
|
|
110
|
+
});
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
/**
|
|
115
|
+
* Lê arquivo com cache
|
|
116
|
+
*/
|
|
117
|
+
async handleRead(projectPath, filePath) {
|
|
118
|
+
if (!filePath) {
|
|
119
|
+
throw new MCPError('VALIDATION_ERROR', 'filePath is required for read action');
|
|
120
|
+
}
|
|
121
|
+
const fullPath = path.resolve(projectPath, filePath);
|
|
122
|
+
// Verificar segurança: path traversal
|
|
123
|
+
if (!fullPath.startsWith(path.resolve(projectPath))) {
|
|
124
|
+
throw new MCPError('SECURITY_ERROR', 'File path must be within project directory');
|
|
125
|
+
}
|
|
126
|
+
// Leitura de arquivo sem cache por enquanto
|
|
127
|
+
try {
|
|
128
|
+
const content = await fs.readFile(fullPath, 'utf8');
|
|
129
|
+
return { success: true, path: fullPath, content, size: content.length };
|
|
130
|
+
}
|
|
131
|
+
catch (error) {
|
|
132
|
+
if (error.code === 'ENOENT') {
|
|
133
|
+
throw new MCPError('FILE_NOT_FOUND', `File not found: ${filePath}`);
|
|
134
|
+
}
|
|
135
|
+
throw new MCPError('FILE_ERROR', `Failed to read file: ${error.message}`);
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Lista diretório com cache
|
|
140
|
+
*/
|
|
141
|
+
async handleList(projectPath, directoryPath) {
|
|
142
|
+
const dirPath = directoryPath ? path.join(projectPath, directoryPath) : projectPath;
|
|
143
|
+
// Verificar segurança: path traversal
|
|
144
|
+
if (!dirPath.startsWith(path.resolve(projectPath))) {
|
|
145
|
+
throw new MCPError('SECURITY_ERROR', 'Directory path must be within project directory');
|
|
146
|
+
}
|
|
147
|
+
// Usar cache para listagem de diretórios
|
|
148
|
+
// Listar diretório sem cache por enquanto
|
|
149
|
+
try {
|
|
150
|
+
const files = await fs.readdir(dirPath, { withFileTypes: true });
|
|
151
|
+
return {
|
|
152
|
+
success: true,
|
|
153
|
+
files: files.map(entry => ({
|
|
154
|
+
name: entry.name,
|
|
155
|
+
isDirectory: entry.isDirectory(),
|
|
156
|
+
isFile: entry.isFile(),
|
|
157
|
+
path: path.join(directoryPath || '', entry.name)
|
|
158
|
+
}))
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
catch (error) {
|
|
162
|
+
if (error.code === 'ENOENT') {
|
|
163
|
+
throw new MCPError('DIRECTORY_NOT_FOUND', `Directory not found: ${directoryPath || 'root'}`);
|
|
164
|
+
}
|
|
165
|
+
throw new MCPError('DIRECTORY_ERROR', `Failed to list directory: ${error.message}`);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Cria arquivo com validação de segurança
|
|
170
|
+
*/
|
|
171
|
+
async handleCreate(projectPath, filePath, content = '') {
|
|
172
|
+
if (!filePath) {
|
|
173
|
+
throw new MCPError('VALIDATION_ERROR', 'filePath is required for create action');
|
|
174
|
+
}
|
|
175
|
+
const targetPath = path.resolve(projectPath, filePath);
|
|
176
|
+
// Verificar segurança: path traversal
|
|
177
|
+
if (!targetPath.startsWith(path.resolve(projectPath))) {
|
|
178
|
+
throw new MCPError('SECURITY_ERROR', 'File path must be within project directory');
|
|
179
|
+
}
|
|
180
|
+
try {
|
|
181
|
+
// Criar diretório se necessário
|
|
182
|
+
const dir = path.dirname(targetPath);
|
|
183
|
+
await fs.mkdir(dir, { recursive: true });
|
|
184
|
+
await fs.writeFile(targetPath, content, 'utf-8');
|
|
185
|
+
return {
|
|
186
|
+
success: true,
|
|
187
|
+
created: true,
|
|
188
|
+
path: targetPath,
|
|
189
|
+
size: content.length,
|
|
190
|
+
};
|
|
191
|
+
}
|
|
192
|
+
catch (error) {
|
|
193
|
+
throw new MCPError('FILE_ERROR', `Failed to create file: ${error.message}`);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
/**
|
|
197
|
+
* Atualiza arquivo com validação de segurança
|
|
198
|
+
*/
|
|
199
|
+
async handleUpdate(projectPath, filePath, content) {
|
|
200
|
+
if (!filePath) {
|
|
201
|
+
throw new MCPError('VALIDATION_ERROR', 'filePath is required for update action');
|
|
202
|
+
}
|
|
203
|
+
if (content === undefined) {
|
|
204
|
+
throw new MCPError('VALIDATION_ERROR', 'content is required for update action');
|
|
205
|
+
}
|
|
206
|
+
const targetPath = path.resolve(projectPath, filePath);
|
|
207
|
+
// Verificar segurança: path traversal
|
|
208
|
+
if (!targetPath.startsWith(path.resolve(projectPath))) {
|
|
209
|
+
throw new MCPError('SECURITY_ERROR', 'File path must be within project directory');
|
|
210
|
+
}
|
|
211
|
+
try {
|
|
212
|
+
// Verificar se arquivo existe
|
|
213
|
+
await fs.access(targetPath);
|
|
214
|
+
await fs.writeFile(targetPath, content, 'utf-8');
|
|
215
|
+
return {
|
|
216
|
+
success: true,
|
|
217
|
+
updated: true,
|
|
218
|
+
path: targetPath,
|
|
219
|
+
size: content.length,
|
|
220
|
+
};
|
|
221
|
+
}
|
|
222
|
+
catch (error) {
|
|
223
|
+
if (error.code === 'ENOENT') {
|
|
224
|
+
throw new MCPError('FILE_NOT_FOUND', 'File does not exist. Use create action.');
|
|
225
|
+
}
|
|
226
|
+
throw new MCPError('FILE_ERROR', `Failed to update file: ${error.message}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
/**
|
|
230
|
+
* Deleta arquivo com validação de segurança
|
|
231
|
+
*/
|
|
232
|
+
async handleDelete(projectPath, filePath) {
|
|
233
|
+
if (!filePath) {
|
|
234
|
+
throw new MCPError('VALIDATION_ERROR', 'filePath is required for delete action');
|
|
235
|
+
}
|
|
236
|
+
const targetPath = path.resolve(projectPath, filePath);
|
|
237
|
+
// Verificar segurança: path traversal
|
|
238
|
+
if (!targetPath.startsWith(path.resolve(projectPath))) {
|
|
239
|
+
throw new MCPError('SECURITY_ERROR', 'File path must be within project directory');
|
|
240
|
+
}
|
|
241
|
+
try {
|
|
242
|
+
await fs.unlink(targetPath);
|
|
243
|
+
return {
|
|
244
|
+
success: true,
|
|
245
|
+
deleted: true,
|
|
246
|
+
path: targetPath,
|
|
247
|
+
};
|
|
248
|
+
}
|
|
249
|
+
catch (error) {
|
|
250
|
+
if (error.code === 'ENOENT') {
|
|
251
|
+
throw new MCPError('FILE_NOT_FOUND', `File not found: ${filePath}`);
|
|
252
|
+
}
|
|
253
|
+
throw new MCPError('FILE_ERROR', `Failed to delete file: ${error.message}`);
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
/**
|
|
257
|
+
* Busca texto em arquivos com cache
|
|
258
|
+
*/
|
|
259
|
+
async handleSearch(projectPath, searchText, searchPath) {
|
|
260
|
+
if (!searchText) {
|
|
261
|
+
throw new MCPError('VALIDATION_ERROR', 'searchText or query is required for search action');
|
|
262
|
+
}
|
|
263
|
+
const searchDir = searchPath ? path.join(projectPath, searchPath) : projectPath;
|
|
264
|
+
// Verificar segurança: path traversal
|
|
265
|
+
if (!searchDir.startsWith(path.resolve(projectPath))) {
|
|
266
|
+
throw new MCPError('SECURITY_ERROR', 'Search path must be within project directory');
|
|
267
|
+
}
|
|
268
|
+
// Busca sem cache por enquanto
|
|
269
|
+
try {
|
|
270
|
+
const results = await this.searchInDirectory(searchDir, searchText);
|
|
271
|
+
return {
|
|
272
|
+
success: true,
|
|
273
|
+
searchText,
|
|
274
|
+
searchPath: searchPath || 'root',
|
|
275
|
+
results: results.slice(0, 50), // Limitar resultados para performance
|
|
276
|
+
totalResults: results.length,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
catch (error) {
|
|
280
|
+
throw new MCPError('SEARCH_ERROR', `Failed to search files: ${error.message}`);
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
/**
|
|
284
|
+
* Busca recursivamente em diretórios
|
|
285
|
+
*/
|
|
286
|
+
async searchInDirectory(dir, searchText) {
|
|
287
|
+
const results = [];
|
|
288
|
+
try {
|
|
289
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
290
|
+
for (const entry of entries) {
|
|
291
|
+
const fullPath = path.join(dir, entry.name);
|
|
292
|
+
if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'node_modules') {
|
|
293
|
+
// Recursivamente buscar em subdiretórios
|
|
294
|
+
const subResults = await this.searchInDirectory(fullPath, searchText);
|
|
295
|
+
results.push(...subResults);
|
|
296
|
+
}
|
|
297
|
+
else if (entry.isFile() && this.isSearchableFile(entry.name)) {
|
|
298
|
+
// Buscar em arquivos
|
|
299
|
+
try {
|
|
300
|
+
const content = await fs.readFile(fullPath, 'utf8');
|
|
301
|
+
const lines = content.split('\n');
|
|
302
|
+
lines.forEach((line, index) => {
|
|
303
|
+
if (line.toLowerCase().includes(searchText.toLowerCase())) {
|
|
304
|
+
results.push({
|
|
305
|
+
file: fullPath,
|
|
306
|
+
line: index + 1,
|
|
307
|
+
text: line.trim(),
|
|
308
|
+
});
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
}
|
|
312
|
+
catch (error) {
|
|
313
|
+
// Ignorar erros de leitura (arquivos binários, etc.)
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
catch (error) {
|
|
319
|
+
// Ignorar erros de acesso
|
|
320
|
+
}
|
|
321
|
+
return results;
|
|
322
|
+
}
|
|
323
|
+
/**
|
|
324
|
+
* Verifica se arquivo é buscável
|
|
325
|
+
*/
|
|
326
|
+
isSearchableFile(filename) {
|
|
327
|
+
const searchableExtensions = [
|
|
328
|
+
'.js', '.ts', '.jsx', '.tsx', '.json', '.md', '.txt', '.yml', '.yaml',
|
|
329
|
+
'.html', '.css', '.scss', '.sass', '.less', '.xml', '.sh', '.bat',
|
|
330
|
+
'.py', '.java', '.cpp', '.c', '.h', '.php', '.rb', '.go', '.rs'
|
|
331
|
+
];
|
|
332
|
+
const ext = path.extname(filename).toLowerCase();
|
|
333
|
+
return searchableExtensions.includes(ext);
|
|
334
|
+
}
|
|
335
|
+
}
|
package/dist/tools/gitFiles.d.ts
CHANGED
|
@@ -62,8 +62,9 @@ export declare class GitFilesTool implements Tool {
|
|
|
62
62
|
size?: undefined;
|
|
63
63
|
updated?: undefined;
|
|
64
64
|
deleted?: undefined;
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
searchText?: undefined;
|
|
66
|
+
results?: undefined;
|
|
67
|
+
totalResults?: undefined;
|
|
67
68
|
} | {
|
|
68
69
|
success: boolean;
|
|
69
70
|
files: string[];
|
|
@@ -73,8 +74,9 @@ export declare class GitFilesTool implements Tool {
|
|
|
73
74
|
size?: undefined;
|
|
74
75
|
updated?: undefined;
|
|
75
76
|
deleted?: undefined;
|
|
76
|
-
|
|
77
|
-
|
|
77
|
+
searchText?: undefined;
|
|
78
|
+
results?: undefined;
|
|
79
|
+
totalResults?: undefined;
|
|
78
80
|
} | {
|
|
79
81
|
success: boolean;
|
|
80
82
|
created: boolean;
|
|
@@ -84,8 +86,9 @@ export declare class GitFilesTool implements Tool {
|
|
|
84
86
|
files?: undefined;
|
|
85
87
|
updated?: undefined;
|
|
86
88
|
deleted?: undefined;
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
searchText?: undefined;
|
|
90
|
+
results?: undefined;
|
|
91
|
+
totalResults?: undefined;
|
|
89
92
|
} | {
|
|
90
93
|
success: boolean;
|
|
91
94
|
updated: boolean;
|
|
@@ -95,8 +98,9 @@ export declare class GitFilesTool implements Tool {
|
|
|
95
98
|
files?: undefined;
|
|
96
99
|
created?: undefined;
|
|
97
100
|
deleted?: undefined;
|
|
98
|
-
|
|
99
|
-
|
|
101
|
+
searchText?: undefined;
|
|
102
|
+
results?: undefined;
|
|
103
|
+
totalResults?: undefined;
|
|
100
104
|
} | {
|
|
101
105
|
success: boolean;
|
|
102
106
|
deleted: boolean;
|
|
@@ -106,15 +110,14 @@ export declare class GitFilesTool implements Tool {
|
|
|
106
110
|
created?: undefined;
|
|
107
111
|
size?: undefined;
|
|
108
112
|
updated?: undefined;
|
|
109
|
-
|
|
110
|
-
|
|
113
|
+
searchText?: undefined;
|
|
114
|
+
results?: undefined;
|
|
115
|
+
totalResults?: undefined;
|
|
111
116
|
} | {
|
|
112
117
|
success: boolean;
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
}[];
|
|
117
|
-
count: number;
|
|
118
|
+
searchText: any;
|
|
119
|
+
results: any[];
|
|
120
|
+
totalResults: number;
|
|
118
121
|
path?: undefined;
|
|
119
122
|
content?: undefined;
|
|
120
123
|
files?: undefined;
|
package/dist/tools/gitFiles.js
CHANGED
|
@@ -146,29 +146,68 @@ export class GitFilesTool {
|
|
|
146
146
|
}
|
|
147
147
|
catch (error) {
|
|
148
148
|
if (error.code === 'ENOENT') {
|
|
149
|
-
throw new MCPError('FILE_NOT_FOUND',
|
|
149
|
+
throw new MCPError('FILE_NOT_FOUND', `File not found: ${filePath}`);
|
|
150
150
|
}
|
|
151
151
|
throw new MCPError('FILE_ERROR', `Failed to delete file: ${error.message}`);
|
|
152
152
|
}
|
|
153
153
|
}
|
|
154
154
|
if (action === 'search') {
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
const
|
|
159
|
-
const
|
|
160
|
-
|
|
155
|
+
const searchText = params.searchText || params.query;
|
|
156
|
+
if (!searchText)
|
|
157
|
+
throw new MCPError('VALIDATION_ERROR', 'searchText or query is required');
|
|
158
|
+
const searchPath = params.searchPath || projectPath;
|
|
159
|
+
const searchDir = path.join(projectPath, searchPath);
|
|
160
|
+
// Security: ensure search path is within projectPath
|
|
161
|
+
if (!searchDir.startsWith(path.resolve(projectPath))) {
|
|
162
|
+
throw new MCPError('SECURITY_ERROR', 'Search path must be within project directory');
|
|
163
|
+
}
|
|
164
|
+
const results = [];
|
|
165
|
+
const searchInDirectory = async (dir) => {
|
|
161
166
|
try {
|
|
162
|
-
const
|
|
163
|
-
|
|
164
|
-
|
|
167
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
168
|
+
for (const entry of entries) {
|
|
169
|
+
const fullPath = path.join(dir, entry.name);
|
|
170
|
+
if (entry.isDirectory() && !entry.name.startsWith('.')) {
|
|
171
|
+
await searchInDirectory(fullPath);
|
|
172
|
+
}
|
|
173
|
+
else if (entry.isFile()) {
|
|
174
|
+
try {
|
|
175
|
+
const content = await fs.readFile(fullPath, 'utf8');
|
|
176
|
+
const lines = content.split('\n');
|
|
177
|
+
lines.forEach((line, index) => {
|
|
178
|
+
if (line.toLowerCase().includes(searchText.toLowerCase())) {
|
|
179
|
+
results.push({
|
|
180
|
+
file: fullPath,
|
|
181
|
+
line: index + 1,
|
|
182
|
+
text: line.trim(),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
});
|
|
186
|
+
}
|
|
187
|
+
catch (error) {
|
|
188
|
+
// Ignore files that can't be read (binary, etc.)
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
}
|
|
165
192
|
}
|
|
166
|
-
catch (
|
|
167
|
-
//
|
|
193
|
+
catch (error) {
|
|
194
|
+
// Ignore directories that can't be accessed
|
|
168
195
|
}
|
|
169
|
-
}
|
|
170
|
-
|
|
196
|
+
};
|
|
197
|
+
await searchInDirectory(searchDir);
|
|
198
|
+
return {
|
|
199
|
+
success: true,
|
|
200
|
+
searchText,
|
|
201
|
+
results: results.slice(0, 50), // Limit results for performance
|
|
202
|
+
totalResults: results.length,
|
|
203
|
+
};
|
|
204
|
+
return {
|
|
205
|
+
success: true,
|
|
206
|
+
searchText,
|
|
207
|
+
results: results.slice(0, 50), // Limit results for performance
|
|
208
|
+
totalResults: results.length,
|
|
209
|
+
};
|
|
171
210
|
}
|
|
172
|
-
throw new MCPError('VALIDATION_ERROR', `
|
|
211
|
+
throw new MCPError('VALIDATION_ERROR', `Unknown action: ${action}`);
|
|
173
212
|
}
|
|
174
213
|
}
|
package/dist/tools/gitFix.js
CHANGED
|
@@ -2,6 +2,7 @@ import simpleGit from 'simple-git';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import fs from 'fs/promises';
|
|
4
4
|
import { existsSync } from 'fs';
|
|
5
|
+
import { buildGitHubUrl, buildGiteaUrl } from '../utils/repoHelpers.js';
|
|
5
6
|
export async function handleGitFix(args) {
|
|
6
7
|
try {
|
|
7
8
|
const { projectPath, githubRepo, giteaRepo, autoDetect = true } = args;
|
|
@@ -106,16 +107,15 @@ export async function handleGitFix(args) {
|
|
|
106
107
|
}
|
|
107
108
|
}
|
|
108
109
|
// Adicionar novos remotes no padrão dual
|
|
109
|
-
const
|
|
110
|
-
const
|
|
111
|
-
const giteaRepoUrl = `${giteaUrl}/${finalGiteaRepo}.git`;
|
|
110
|
+
const githubUrl = buildGitHubUrl(finalGithubRepo.split('/')[0], finalGithubRepo.split('/')[1]);
|
|
111
|
+
const giteaUrlFull = buildGiteaUrl(finalGiteaRepo.split('/')[0], finalGiteaRepo.split('/')[1]);
|
|
112
112
|
await git.addRemote('github', githubUrl);
|
|
113
|
-
result.fixed.push(`✅ Adicionado remote GitHub: ${githubUrl}`);
|
|
114
|
-
await git.addRemote('gitea',
|
|
115
|
-
result.fixed.push(`✅ Adicionado remote Gitea: ${
|
|
113
|
+
result.fixed.push(`✅ Adicionado remote GitHub: ${githubUrl.replace(/oauth2:[^@]+@/, 'oauth2:***@')}`);
|
|
114
|
+
await git.addRemote('gitea', giteaUrlFull);
|
|
115
|
+
result.fixed.push(`✅ Adicionado remote Gitea: ${giteaUrlFull.replace(/:[^:]+@/, ':***@')}`);
|
|
116
116
|
// Configurar origin como push múltiplo
|
|
117
117
|
await git.addRemote('origin', githubUrl);
|
|
118
|
-
await git.addConfig('remote.origin.pushurl',
|
|
118
|
+
await git.addConfig('remote.origin.pushurl', giteaUrlFull, false, 'local');
|
|
119
119
|
result.fixed.push(`✅ Configurado origin para push dual (GitHub + Gitea)`);
|
|
120
120
|
// Capturar remotes depois
|
|
121
121
|
const remotesAfter = await git.getRemotes(true);
|
|
@@ -67,19 +67,19 @@ export class GitFixTool {
|
|
|
67
67
|
properties: {
|
|
68
68
|
projectPath: {
|
|
69
69
|
type: "string",
|
|
70
|
-
description: "
|
|
70
|
+
description: "The absolute path to the Git repository directory on the local filesystem. This should be the root directory containing the .git folder. For example: '/home/user/my-project' on Linux/Mac or 'C:\\Users\\user\\my-project' on Windows."
|
|
71
71
|
},
|
|
72
72
|
githubRepo: {
|
|
73
73
|
type: "string",
|
|
74
|
-
description: "GitHub repo in format
|
|
74
|
+
description: "GitHub repo in format \"owner/repo\" (auto-detected if not provided)"
|
|
75
75
|
},
|
|
76
76
|
giteaRepo: {
|
|
77
77
|
type: "string",
|
|
78
|
-
description: "Gitea repo in format
|
|
78
|
+
description: "Gitea repo in format \"owner/repo\" (auto-detected if not provided)"
|
|
79
79
|
},
|
|
80
80
|
autoDetect: {
|
|
81
81
|
type: "boolean",
|
|
82
|
-
description: "Auto-detect repos from existing remotes (
|
|
82
|
+
description: "Auto-detect repos from existing remotes (default: true)"
|
|
83
83
|
}
|
|
84
84
|
},
|
|
85
85
|
required: ["projectPath"],
|
|
@@ -10,36 +10,13 @@ export declare class GitHistoryTool implements Tool {
|
|
|
10
10
|
inputSchema: {
|
|
11
11
|
type: "object";
|
|
12
12
|
properties: {
|
|
13
|
-
action: {
|
|
14
|
-
type: string;
|
|
15
|
-
enum: string[];
|
|
16
|
-
description: string;
|
|
17
|
-
};
|
|
18
13
|
projectPath: {
|
|
19
14
|
type: string;
|
|
20
15
|
description: string;
|
|
21
16
|
};
|
|
22
|
-
|
|
23
|
-
type: string;
|
|
24
|
-
description: string;
|
|
25
|
-
};
|
|
26
|
-
message: {
|
|
27
|
-
type: string;
|
|
28
|
-
description: string;
|
|
29
|
-
};
|
|
30
|
-
title: {
|
|
31
|
-
type: string;
|
|
32
|
-
description: string;
|
|
33
|
-
};
|
|
34
|
-
tags: {
|
|
35
|
-
type: string;
|
|
36
|
-
items: {
|
|
37
|
-
type: string;
|
|
38
|
-
};
|
|
39
|
-
description: string;
|
|
40
|
-
};
|
|
41
|
-
category: {
|
|
17
|
+
action: {
|
|
42
18
|
type: string;
|
|
19
|
+
enum: string[];
|
|
43
20
|
description: string;
|
|
44
21
|
};
|
|
45
22
|
owner: {
|