@andrebuzeli/git-mcp 5.5.2 → 5.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/server.d.ts CHANGED
@@ -1,6 +1,7 @@
1
1
  import { ProviderManager } from './providers/providerManager';
2
- import { Tool } from './types';
2
+ import { Tool, Resource } from './types';
3
3
  export declare function createServer(opts: {
4
4
  tools: Tool[];
5
5
  providerManager: ProviderManager;
6
+ resources?: Resource[];
6
7
  }): import("express-serve-static-core").Express;
package/dist/server.js CHANGED
@@ -13,6 +13,39 @@ function createServer(opts) {
13
13
  const toolRegistry = new Map();
14
14
  for (const t of opts.tools)
15
15
  toolRegistry.set(t.name, t);
16
+ const resourceRegistry = new Map();
17
+ if (opts.resources) {
18
+ for (const r of opts.resources)
19
+ resourceRegistry.set(r.uri, r);
20
+ }
21
+ // List all available resources
22
+ app.get('/resources', (req, res) => {
23
+ const resources = Array.from(resourceRegistry.values()).map(r => ({
24
+ uri: r.uri,
25
+ name: r.name,
26
+ description: r.description,
27
+ mimeType: r.mimeType
28
+ }));
29
+ res.json({ success: true, resources });
30
+ });
31
+ // Get specific resource
32
+ app.get('/resources/:uri(*)', (req, res) => {
33
+ const uri = req.params.uri;
34
+ const resource = resourceRegistry.get(uri);
35
+ if (!resource) {
36
+ return res.status(404).json((0, errors_1.createErrorResponse)('RESOURCE_NOT_FOUND', `Resource not found: ${uri}`));
37
+ }
38
+ res.json({
39
+ success: true,
40
+ resource: {
41
+ uri: resource.uri,
42
+ name: resource.name,
43
+ description: resource.description,
44
+ mimeType: resource.mimeType,
45
+ content: resource.content
46
+ }
47
+ });
48
+ });
16
49
  app.post('/call', async (req, res) => {
17
50
  const { tool, params } = req.body ?? {};
18
51
  if (!tool)
@@ -25,9 +25,9 @@ export declare class GitBranchesTool implements Tool {
25
25
  commits?: undefined;
26
26
  diff?: undefined;
27
27
  } | {
28
+ success: boolean;
28
29
  branch: string;
29
30
  current: boolean;
30
- success?: undefined;
31
31
  branches?: undefined;
32
32
  deleted?: undefined;
33
33
  result?: undefined;
@@ -58,11 +58,11 @@ export declare class GitBranchesTool implements Tool {
58
58
  commits?: undefined;
59
59
  diff?: undefined;
60
60
  } | {
61
+ success: boolean;
61
62
  baseBranch: any;
62
63
  compareBranch: any;
63
64
  commits: readonly (import("simple-git").DefaultLogFields & import("simple-git").ListLogLine)[];
64
65
  diff: string;
65
- success?: undefined;
66
66
  branch?: undefined;
67
67
  branches?: undefined;
68
68
  current?: undefined;
@@ -53,7 +53,7 @@ class GitBranchesTool {
53
53
  'Use git branch -a to see all available branches',
54
54
  ]);
55
55
  }
56
- return { branch, current: branches.current === branchName };
56
+ return { success: true, branch, current: branches.current === branchName };
57
57
  }
58
58
  case 'delete': {
59
59
  (0, safetyController_1.requireConfirmationIfDestructive)('delete', params);
@@ -83,6 +83,7 @@ class GitBranchesTool {
83
83
  const diff = await git.diff([`${baseBranch}...${compareBranch}`]);
84
84
  const log = await git.log({ from: baseBranch, to: compareBranch });
85
85
  return {
86
+ success: true,
86
87
  baseBranch,
87
88
  compareBranch,
88
89
  commits: log.all,
@@ -3,20 +3,26 @@ export declare class GitFilesTool implements Tool {
3
3
  name: string;
4
4
  description: string;
5
5
  handle(params: Record<string, any>): Promise<{
6
+ success: boolean;
6
7
  path: string;
7
8
  content: string;
8
9
  files?: undefined;
9
10
  matches?: undefined;
11
+ count?: undefined;
10
12
  } | {
13
+ success: boolean;
11
14
  files: string[];
12
15
  path?: undefined;
13
16
  content?: undefined;
14
17
  matches?: undefined;
18
+ count?: undefined;
15
19
  } | {
20
+ success: boolean;
16
21
  matches: {
17
22
  file: string;
18
23
  snippet: string;
19
24
  }[];
25
+ count: number;
20
26
  path?: undefined;
21
27
  content?: undefined;
22
28
  files?: undefined;
@@ -20,11 +20,12 @@ class GitFilesTool {
20
20
  const fullPath = path_1.default.resolve(projectPath, params.filePath ?? '');
21
21
  if (action === 'read') {
22
22
  const content = await promises_1.default.readFile(fullPath, 'utf8');
23
- return { path: fullPath, content };
23
+ return { success: true, path: fullPath, content };
24
24
  }
25
25
  if (action === 'list') {
26
- const files = await promises_1.default.readdir(projectPath);
27
- return { files };
26
+ const dirPath = params.directoryPath ? path_1.default.join(projectPath, params.directoryPath) : projectPath;
27
+ const files = await promises_1.default.readdir(dirPath);
28
+ return { success: true, files };
28
29
  }
29
30
  // Blocked operations
30
31
  if (['create', 'update', 'delete'].includes(action)) {
@@ -32,20 +33,21 @@ class GitFilesTool {
32
33
  }
33
34
  if (action === 'search') {
34
35
  // Simple string search within files in projectPath (non-recursive)
35
- const files = await promises_1.default.readdir(projectPath);
36
- const q = params.query ?? '';
36
+ const searchPath = params.searchPath ? path_1.default.join(projectPath, params.searchPath) : projectPath;
37
+ const files = await promises_1.default.readdir(searchPath);
38
+ const q = params.searchText ?? params.query ?? '';
37
39
  const matches = [];
38
40
  for (const f of files) {
39
41
  try {
40
- const txt = await promises_1.default.readFile(path_1.default.join(projectPath, f), 'utf8');
42
+ const txt = await promises_1.default.readFile(path_1.default.join(searchPath, f), 'utf8');
41
43
  if (txt.includes(q))
42
- matches.push({ file: f, snippet: txt.substr(txt.indexOf(q), 200) });
44
+ matches.push({ file: f, snippet: txt.substring(txt.indexOf(q), txt.indexOf(q) + 200) });
43
45
  }
44
46
  catch (e) {
45
47
  // ignore binary/unreadable
46
48
  }
47
49
  }
48
- return { matches };
50
+ return { success: true, matches, count: matches.length };
49
51
  }
50
52
  throw new errors_1.MCPError('VALIDATION_ERROR', `Unsupported action: ${action}`);
51
53
  }
@@ -0,0 +1,17 @@
1
+ import { Tool, MCPContext } from '../types';
2
+ /**
3
+ * Git History Tool - Mantém histórico detalhado de TODAS alterações
4
+ * Modo DUAL automático - cria issues de histórico em GitHub e Gitea
5
+ * Rastreabilidade completa de cada mudança local
6
+ */
7
+ export declare class GitHistoryTool implements Tool {
8
+ name: string;
9
+ description: string;
10
+ handle(params: Record<string, any>, ctx: MCPContext): Promise<any>;
11
+ private trackChange;
12
+ private listLocalHistory;
13
+ private syncHistory;
14
+ private generateReport;
15
+ private saveLocalHistory;
16
+ private formatHistoryBody;
17
+ }
@@ -0,0 +1,365 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.GitHistoryTool = void 0;
40
+ const simple_git_1 = __importDefault(require("simple-git"));
41
+ const errors_1 = require("../utils/errors");
42
+ const axios_1 = __importDefault(require("axios"));
43
+ const fs = __importStar(require("fs/promises"));
44
+ const path = __importStar(require("path"));
45
+ /**
46
+ * Git History Tool - Mantém histórico detalhado de TODAS alterações
47
+ * Modo DUAL automático - cria issues de histórico em GitHub e Gitea
48
+ * Rastreabilidade completa de cada mudança local
49
+ */
50
+ class GitHistoryTool {
51
+ constructor() {
52
+ this.name = 'git-history';
53
+ this.description = 'Maintain detailed history of all project changes with remote tracking via issues - automatic dual-provider execution';
54
+ }
55
+ async handle(params, ctx) {
56
+ const projectPath = params.projectPath;
57
+ if (!projectPath) {
58
+ throw new errors_1.MCPError('VALIDATION_ERROR', 'projectPath is required');
59
+ }
60
+ const action = params.action || 'track';
61
+ const git = (0, simple_git_1.default)({ baseDir: projectPath });
62
+ switch (action) {
63
+ case 'track': {
64
+ // Rastrear mudança específica e criar histórico remoto
65
+ return await this.trackChange(git, projectPath, params, ctx);
66
+ }
67
+ case 'list': {
68
+ // Listar histórico local
69
+ return await this.listLocalHistory(projectPath);
70
+ }
71
+ case 'sync': {
72
+ // Sincronizar histórico local com remoto
73
+ return await this.syncHistory(git, projectPath, params, ctx);
74
+ }
75
+ case 'report': {
76
+ // Gerar relatório consolidado
77
+ return await this.generateReport(projectPath, params, ctx);
78
+ }
79
+ default:
80
+ throw new errors_1.MCPError('VALIDATION_ERROR', `Unsupported action: ${action}`);
81
+ }
82
+ }
83
+ async trackChange(git, projectPath, params, ctx) {
84
+ const results = {
85
+ success: true,
86
+ timestamp: new Date().toISOString(),
87
+ projectPath,
88
+ action: 'track',
89
+ providers: {},
90
+ traceability: {
91
+ changeDetails: {},
92
+ localHistory: null,
93
+ remoteHistory: {},
94
+ errors: [],
95
+ }
96
+ };
97
+ try {
98
+ // 1. Coletar detalhes da mudança
99
+ const status = await git.status();
100
+ const log = await git.log({ maxCount: 1 });
101
+ const lastCommit = log.latest;
102
+ results.traceability.changeDetails = {
103
+ branch: status.current,
104
+ lastCommit: lastCommit ? {
105
+ hash: lastCommit.hash,
106
+ date: lastCommit.date,
107
+ message: lastCommit.message,
108
+ author: lastCommit.author_name,
109
+ email: lastCommit.author_email,
110
+ } : null,
111
+ currentStatus: {
112
+ modified: status.modified,
113
+ created: status.created,
114
+ deleted: status.deleted,
115
+ staged: status.staged,
116
+ conflicted: status.conflicted,
117
+ },
118
+ description: params.description || params.message || 'Change tracked via git-history',
119
+ tags: params.tags || [],
120
+ category: params.category || 'general',
121
+ };
122
+ // 2. Salvar no histórico local
123
+ const localHistoryFile = await this.saveLocalHistory(projectPath, results.traceability.changeDetails);
124
+ results.traceability.localHistory = localHistoryFile;
125
+ // 3. Criar issue de histórico em ambos providers
126
+ const githubOwner = params.owner || process.env.GITHUB_USERNAME;
127
+ const giteaOwner = params.owner || process.env.GITEA_USERNAME;
128
+ const repo = params.repo || path.basename(projectPath);
129
+ const historyTitle = params.title || `[${results.traceability.changeDetails.category}] ${results.traceability.changeDetails.description}`;
130
+ const historyBody = this.formatHistoryBody(results.traceability.changeDetails, params);
131
+ // GITHUB
132
+ if (ctx.providerManager.github) {
133
+ try {
134
+ const issue = await ctx.providerManager.github.rest.issues.create({
135
+ owner: githubOwner,
136
+ repo,
137
+ title: historyTitle,
138
+ body: historyBody,
139
+ labels: ['git-history', results.traceability.changeDetails.category, ...results.traceability.changeDetails.tags],
140
+ });
141
+ results.providers.github = {
142
+ success: true,
143
+ issue: {
144
+ number: issue.data.number,
145
+ url: issue.data.html_url,
146
+ id: issue.data.id,
147
+ }
148
+ };
149
+ }
150
+ catch (err) {
151
+ results.providers.github = {
152
+ success: false,
153
+ error: err.message,
154
+ };
155
+ results.traceability.errors.push({
156
+ provider: 'github',
157
+ error: err.message,
158
+ timestamp: new Date().toISOString(),
159
+ });
160
+ }
161
+ }
162
+ // GITEA
163
+ if (ctx.providerManager.giteaBaseUrl) {
164
+ try {
165
+ const issue = await axios_1.default.post(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repo}/issues`, {
166
+ title: historyTitle,
167
+ body: historyBody,
168
+ }, { headers: { Authorization: `token ${ctx.providerManager.giteaToken}` } });
169
+ results.providers.gitea = {
170
+ success: true,
171
+ issue: {
172
+ number: issue.data.number,
173
+ url: issue.data.html_url,
174
+ id: issue.data.id,
175
+ }
176
+ };
177
+ }
178
+ catch (err) {
179
+ results.providers.gitea = {
180
+ success: false,
181
+ error: err.message,
182
+ };
183
+ results.traceability.errors.push({
184
+ provider: 'gitea',
185
+ error: err.message,
186
+ timestamp: new Date().toISOString(),
187
+ });
188
+ }
189
+ }
190
+ return results;
191
+ }
192
+ catch (err) {
193
+ throw new errors_1.MCPError('TRACK_ERROR', `Failed to track change: ${err.message}`);
194
+ }
195
+ }
196
+ async listLocalHistory(projectPath) {
197
+ try {
198
+ const historyDir = path.join(projectPath, '.git-mcp-history');
199
+ try {
200
+ await fs.access(historyDir);
201
+ }
202
+ catch {
203
+ return {
204
+ success: true,
205
+ history: [],
206
+ message: 'No history found',
207
+ };
208
+ }
209
+ const files = await fs.readdir(historyDir);
210
+ const historyFiles = files.filter(f => f.startsWith('history-') && f.endsWith('.json'));
211
+ const history = await Promise.all(historyFiles.map(async (file) => {
212
+ const content = await fs.readFile(path.join(historyDir, file), 'utf-8');
213
+ return JSON.parse(content);
214
+ }));
215
+ return {
216
+ success: true,
217
+ count: history.length,
218
+ history: history.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime()),
219
+ };
220
+ }
221
+ catch (err) {
222
+ throw new errors_1.MCPError('LIST_ERROR', `Failed to list history: ${err.message}`);
223
+ }
224
+ }
225
+ async syncHistory(git, projectPath, params, ctx) {
226
+ const results = {
227
+ success: true,
228
+ timestamp: new Date().toISOString(),
229
+ action: 'sync',
230
+ synced: 0,
231
+ providers: { github: {}, gitea: {} },
232
+ errors: [],
233
+ };
234
+ try {
235
+ const localHistory = await this.listLocalHistory(projectPath);
236
+ const githubOwner = params.owner || process.env.GITHUB_USERNAME;
237
+ const giteaOwner = params.owner || process.env.GITEA_USERNAME;
238
+ const repo = params.repo || path.basename(projectPath);
239
+ for (const historyEntry of localHistory.history) {
240
+ if (!historyEntry.synced) {
241
+ try {
242
+ const title = `[history-sync] ${historyEntry.description}`;
243
+ const body = this.formatHistoryBody(historyEntry, params);
244
+ // Sync to GitHub
245
+ if (ctx.providerManager.github) {
246
+ try {
247
+ await ctx.providerManager.github.rest.issues.create({
248
+ owner: githubOwner,
249
+ repo,
250
+ title,
251
+ body,
252
+ labels: ['git-history', 'synced'],
253
+ });
254
+ results.synced++;
255
+ }
256
+ catch (err) {
257
+ results.errors.push({ provider: 'github', error: err.message });
258
+ }
259
+ }
260
+ // Sync to Gitea
261
+ if (ctx.providerManager.giteaBaseUrl) {
262
+ try {
263
+ await axios_1.default.post(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repo}/issues`, { title, body }, { headers: { Authorization: `token ${ctx.providerManager.giteaToken}` } });
264
+ results.synced++;
265
+ }
266
+ catch (err) {
267
+ results.errors.push({ provider: 'gitea', error: err.message });
268
+ }
269
+ }
270
+ }
271
+ catch (err) {
272
+ results.errors.push({ entry: historyEntry.timestamp, error: err.message });
273
+ }
274
+ }
275
+ }
276
+ return results;
277
+ }
278
+ catch (err) {
279
+ throw new errors_1.MCPError('SYNC_ERROR', `Failed to sync history: ${err.message}`);
280
+ }
281
+ }
282
+ async generateReport(projectPath, params, ctx) {
283
+ try {
284
+ const localHistory = await this.listLocalHistory(projectPath);
285
+ const report = {
286
+ success: true,
287
+ timestamp: new Date().toISOString(),
288
+ projectPath,
289
+ summary: {
290
+ totalChanges: localHistory.count,
291
+ byCategory: {},
292
+ byMonth: {},
293
+ recentChanges: localHistory.history.slice(0, 10),
294
+ }
295
+ };
296
+ // Agrupar por categoria
297
+ localHistory.history.forEach((entry) => {
298
+ const cat = entry.category || 'general';
299
+ report.summary.byCategory[cat] = (report.summary.byCategory[cat] || 0) + 1;
300
+ const month = new Date(entry.timestamp).toISOString().slice(0, 7);
301
+ report.summary.byMonth[month] = (report.summary.byMonth[month] || 0) + 1;
302
+ });
303
+ return report;
304
+ }
305
+ catch (err) {
306
+ throw new errors_1.MCPError('REPORT_ERROR', `Failed to generate report: ${err.message}`);
307
+ }
308
+ }
309
+ async saveLocalHistory(projectPath, changeDetails) {
310
+ const historyDir = path.join(projectPath, '.git-mcp-history');
311
+ await fs.mkdir(historyDir, { recursive: true });
312
+ const historyFile = path.join(historyDir, `history-${Date.now()}.json`);
313
+ const historyEntry = {
314
+ timestamp: new Date().toISOString(),
315
+ ...changeDetails,
316
+ synced: false,
317
+ };
318
+ await fs.writeFile(historyFile, JSON.stringify(historyEntry, null, 2), 'utf-8');
319
+ return historyFile;
320
+ }
321
+ formatHistoryBody(changeDetails, params) {
322
+ return `
323
+ ## 📜 Git History Entry
324
+
325
+ **Timestamp:** ${changeDetails.timestamp || new Date().toISOString()}
326
+ **Category:** ${changeDetails.category || 'general'}
327
+ **Branch:** ${changeDetails.branch || 'N/A'}
328
+
329
+ ### 📝 Description
330
+ ${changeDetails.description || 'No description provided'}
331
+
332
+ ### 🔧 Last Commit
333
+ ${changeDetails.lastCommit ? `
334
+ - **Hash:** \`${changeDetails.lastCommit.hash}\`
335
+ - **Author:** ${changeDetails.lastCommit.author} (${changeDetails.lastCommit.email})
336
+ - **Date:** ${changeDetails.lastCommit.date}
337
+ - **Message:** ${changeDetails.lastCommit.message}
338
+ ` : 'No recent commit'}
339
+
340
+ ### 📊 Current Status
341
+ ${changeDetails.currentStatus ? `
342
+ - **Modified:** ${changeDetails.currentStatus.modified?.length || 0} files
343
+ - **Created:** ${changeDetails.currentStatus.created?.length || 0} files
344
+ - **Deleted:** ${changeDetails.currentStatus.deleted?.length || 0} files
345
+ - **Staged:** ${changeDetails.currentStatus.staged?.length || 0} files
346
+ - **Conflicted:** ${changeDetails.currentStatus.conflicted?.length || 0} files
347
+ ` : 'No status available'}
348
+
349
+ ${changeDetails.currentStatus?.modified?.length > 0 ? `
350
+ ### 📁 Modified Files
351
+ ${changeDetails.currentStatus.modified.map((f) => `- ${f}`).join('\n')}
352
+ ` : ''}
353
+
354
+ ${changeDetails.tags?.length > 0 ? `
355
+ ### 🏷️ Tags
356
+ ${changeDetails.tags.map((t) => `\`${t}\``).join(', ')}
357
+ ` : ''}
358
+
359
+ ---
360
+ *Generated by git-mcp git-history tool*
361
+ *Tracked at: ${changeDetails.timestamp || new Date().toISOString()}*
362
+ `;
363
+ }
364
+ }
365
+ exports.GitHistoryTool = GitHistoryTool;
@@ -2,20 +2,5 @@ import { Tool, MCPContext } from '../types';
2
2
  export declare class GitIssuesTool implements Tool {
3
3
  name: string;
4
4
  description: string;
5
- handle(params: Record<string, any>, ctx: MCPContext): Promise<{
6
- success: boolean;
7
- issue: any;
8
- issues?: undefined;
9
- comment?: undefined;
10
- } | {
11
- success: boolean;
12
- issues: any;
13
- issue?: undefined;
14
- comment?: undefined;
15
- } | {
16
- success: boolean;
17
- comment: any;
18
- issue?: undefined;
19
- issues?: undefined;
20
- }>;
5
+ handle(params: Record<string, any>, ctx: MCPContext): Promise<any>;
21
6
  }