@andrebuzeli/git-mcp 10.0.7 → 10.0.9

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.
Files changed (45) hide show
  1. package/LICENSE +0 -0
  2. package/README.md +1 -1
  3. package/dist/providers/providerManager.js +2 -4
  4. package/dist/scripts/test_e2e.d.ts +1 -0
  5. package/dist/scripts/test_e2e.js +199 -0
  6. package/dist/scripts/test_exhaustive.d.ts +1 -0
  7. package/dist/scripts/test_exhaustive.js +275 -0
  8. package/dist/scripts/test_gitea_creation.d.ts +1 -0
  9. package/dist/scripts/test_gitea_creation.js +116 -0
  10. package/dist/scripts/verify_setup.d.ts +1 -0
  11. package/dist/scripts/verify_setup.js +61 -0
  12. package/dist/tools/gitAnalytics.js +3 -3
  13. package/dist/tools/gitBackup.d.ts +2 -2
  14. package/dist/tools/gitBackup.js +5 -5
  15. package/dist/tools/gitBranches.js +40 -29
  16. package/dist/tools/gitConfig.d.ts +2 -2
  17. package/dist/tools/gitConfig.js +12 -13
  18. package/dist/tools/gitFix.d.ts +2 -1
  19. package/dist/tools/gitFix.js +20 -24
  20. package/dist/tools/gitFix.tool.d.ts +2 -2
  21. package/dist/tools/gitFix.tool.js +2 -2
  22. package/dist/tools/gitHistory.js +4 -5
  23. package/dist/tools/gitIssues.js +8 -8
  24. package/dist/tools/gitMonitor.js +28 -33
  25. package/dist/tools/gitPulls.js +8 -8
  26. package/dist/tools/gitRelease.js +5 -6
  27. package/dist/tools/gitRemote.d.ts +1 -11
  28. package/dist/tools/gitRemote.js +14 -20
  29. package/dist/tools/gitReset.js +6 -7
  30. package/dist/tools/gitStash.d.ts +1 -12
  31. package/dist/tools/gitStash.js +11 -22
  32. package/dist/tools/gitSync.js +44 -36
  33. package/dist/tools/gitTags.d.ts +8 -0
  34. package/dist/tools/gitTags.js +44 -34
  35. package/dist/tools/gitUpdate.d.ts +27 -0
  36. package/dist/tools/gitUpdate.js +61 -34
  37. package/dist/tools/gitUpload.js +29 -44
  38. package/dist/tools/gitWorkflow.js +27 -46
  39. package/dist/utils/gitAdapter.js +10 -0
  40. package/dist/utils/repoHelpers.js +7 -12
  41. package/package.json +2 -3
  42. package/dist/server-new.d.ts +0 -2
  43. package/dist/server-new.js +0 -224
  44. package/dist/tools/gitFiles-new.d.ts +0 -89
  45. package/dist/tools/gitFiles-new.js +0 -335
@@ -1,4 +1,3 @@
1
- import simpleGit from 'simple-git';
2
1
  import { MCPError } from '../utils/errors.js';
3
2
  import axios from 'axios';
4
3
  import * as fs from 'fs/promises';
@@ -42,11 +41,13 @@ export class GitUploadTool {
42
41
  if (!projectPath) {
43
42
  throw new MCPError('VALIDATION_ERROR', 'projectPath is required');
44
43
  }
44
+ if (!ctx.gitAdapter) {
45
+ throw new MCPError('INTERNAL_ERROR', 'Git adapter not available');
46
+ }
45
47
  const repoName = params.repoName || getRepoNameFromPath(projectPath);
46
48
  const description = params.description || `Project uploaded via git-upload at ${new Date().toISOString()}`;
47
49
  const isPrivate = params.private !== undefined ? params.private : true;
48
50
  const branch = params.branch || 'master';
49
- const git = simpleGit({ baseDir: projectPath });
50
51
  // Resultado com rastreabilidade completa
51
52
  const results = {
52
53
  success: true,
@@ -64,20 +65,16 @@ export class GitUploadTool {
64
65
  try {
65
66
  // 0. Verificar se é um repositório Git, se não for, inicializar
66
67
  try {
67
- await git.status();
68
+ await ctx.gitAdapter.status(projectPath);
68
69
  }
69
70
  catch (err) {
70
- if (err.message.includes('not a git repository')) {
71
- results.traceability.uploadSteps.push({
72
- step: 0,
73
- action: 'init_git_repository',
74
- timestamp: new Date().toISOString(),
75
- });
76
- await git.init();
77
- }
78
- else {
79
- throw err;
80
- }
71
+ // If status fails, assume not a git repo or other error
72
+ results.traceability.uploadSteps.push({
73
+ step: 0,
74
+ action: 'init_git_repository',
75
+ timestamp: new Date().toISOString(),
76
+ });
77
+ await ctx.gitAdapter.init(projectPath, branch);
81
78
  }
82
79
  // 1. Verificar status local
83
80
  results.traceability.uploadSteps.push({
@@ -85,7 +82,7 @@ export class GitUploadTool {
85
82
  action: 'check_local_status',
86
83
  timestamp: new Date().toISOString(),
87
84
  });
88
- const status = await git.status();
85
+ const status = await ctx.gitAdapter.status(projectPath);
89
86
  results.traceability.localChanges = {
90
87
  modified: status.modified,
91
88
  created: status.created,
@@ -93,17 +90,17 @@ export class GitUploadTool {
93
90
  renamed: status.renamed,
94
91
  staged: status.staged,
95
92
  conflicted: status.conflicted,
96
- isClean: status.isClean(),
93
+ isClean: status.isClean,
97
94
  };
98
95
  // 2. Verificar se tem mudanças
99
- if (!status.isClean()) {
96
+ if (!status.isClean) {
100
97
  results.traceability.uploadSteps.push({
101
98
  step: 2,
102
99
  action: 'stage_all_changes',
103
100
  timestamp: new Date().toISOString(),
104
101
  files: [...status.modified, ...status.created, ...status.deleted],
105
102
  });
106
- await git.add('.');
103
+ await ctx.gitAdapter.add(projectPath, ['.']);
107
104
  }
108
105
  // 3. Commit com rastreabilidade
109
106
  const commitMessage = params.commitMessage || `[git-upload] Upload project at ${new Date().toISOString()}`;
@@ -115,7 +112,13 @@ export class GitUploadTool {
115
112
  });
116
113
  let commitResult;
117
114
  try {
118
- commitResult = await git.commit(commitMessage);
115
+ const sha = await ctx.gitAdapter.commit(projectPath, commitMessage);
116
+ commitResult = {
117
+ commit: sha,
118
+ summary: { changes: 0, insertions: 0, deletions: 0 },
119
+ branch: await ctx.gitAdapter.getCurrentBranch(projectPath),
120
+ author: { name: 'unknown', email: 'unknown' }
121
+ };
119
122
  results.traceability.commit = {
120
123
  hash: commitResult.commit,
121
124
  summary: commitResult.summary,
@@ -124,7 +127,7 @@ export class GitUploadTool {
124
127
  };
125
128
  }
126
129
  catch (err) {
127
- if (err.message.includes('nothing to commit')) {
130
+ if (err.message.includes('nothing to commit') || (status.isClean && status.staged.length === 0)) {
128
131
  results.traceability.commit = { message: 'No changes to commit' };
129
132
  }
130
133
  else {
@@ -174,12 +177,12 @@ export class GitUploadTool {
174
177
  };
175
178
  }
176
179
  // Adicionar remote se necessário (URL LIMPA sem token)
177
- const remotes = await git.getRemotes(true);
180
+ const remotes = await ctx.gitAdapter.listRemotes(projectPath);
178
181
  const githubRemote = remotes.find(r => r.name === 'github');
179
182
  if (!githubRemote) {
180
183
  // Usar URL limpa sem token para segurança
181
184
  const remoteUrl = `https://github.com/${githubOwner}/${repoName}.git`;
182
- await git.addRemote('github', remoteUrl);
185
+ await ctx.gitAdapter.addRemote(projectPath, 'github', remoteUrl);
183
186
  results.traceability.uploadSteps.push({
184
187
  step: 5,
185
188
  action: 'add_github_remote',
@@ -195,19 +198,12 @@ export class GitUploadTool {
195
198
  branch,
196
199
  });
197
200
  try {
198
- // Configurar credential helper temporário para este push
199
- await git.addConfig('credential.https://github.com.helper', 'store');
200
- await git.addConfig(`credential.https://github.com.username`, githubOwner);
201
201
  // Push com --set-upstream mas SEM --force (segurança)
202
- await git.push('github', branch, ['--set-upstream']);
203
- // Limpar credential helper após push
204
- await git.addConfig('credential.https://github.com.helper', '');
202
+ await ctx.gitAdapter.push(projectPath, 'github', branch, false, true);
205
203
  results.providers.github.pushed = true;
206
204
  results.providers.github.pushStatus = 'success';
207
205
  }
208
206
  catch (pushErr) {
209
- // Limpar credential helper em caso de erro
210
- await git.addConfig('credential.https://github.com.helper', '');
211
207
  results.providers.github.pushed = false;
212
208
  results.providers.github.pushError = pushErr.message;
213
209
  results.traceability.errors.push({
@@ -271,14 +267,14 @@ export class GitUploadTool {
271
267
  };
272
268
  }
273
269
  // Adicionar remote se necessário (URL LIMPA sem token)
274
- const remotes = await git.getRemotes(true);
270
+ const remotes = await ctx.gitAdapter.listRemotes(projectPath);
275
271
  const giteaRemote = remotes.find(r => r.name === 'gitea');
276
272
  if (!giteaRemote) {
277
273
  // Usar URL limpa sem token para segurança
278
274
  const baseUrl = ctx.providerManager.giteaBaseUrl;
279
275
  const cleanUrl = baseUrl.replace(/\/$/, ''); // Remover barra final se houver
280
276
  const remoteUrl = `${cleanUrl}/${giteaOwner}/${repoName}.git`;
281
- await git.addRemote('gitea', remoteUrl);
277
+ await ctx.gitAdapter.addRemote(projectPath, 'gitea', remoteUrl);
282
278
  results.traceability.uploadSteps.push({
283
279
  step: 8,
284
280
  action: 'add_gitea_remote',
@@ -294,23 +290,12 @@ export class GitUploadTool {
294
290
  branch,
295
291
  });
296
292
  try {
297
- const baseUrl = ctx.providerManager.giteaBaseUrl;
298
- const cleanUrl = baseUrl.replace(/\/$/, '');
299
- // Configurar credential helper temporário
300
- await git.addConfig(`credential.${cleanUrl}.helper`, 'store');
301
- await git.addConfig(`credential.${cleanUrl}.username`, giteaOwner);
302
293
  // Push com --set-upstream mas SEM --force (segurança)
303
- await git.push('gitea', branch, ['--set-upstream']);
304
- // Limpar credential helper após push
305
- await git.addConfig(`credential.${cleanUrl}.helper`, '');
294
+ await ctx.gitAdapter.push(projectPath, 'gitea', branch, false, true);
306
295
  results.providers.gitea.pushed = true;
307
296
  results.providers.gitea.pushStatus = 'success';
308
297
  }
309
298
  catch (pushErr) {
310
- // Limpar credential helper em caso de erro
311
- const baseUrl = ctx.providerManager.giteaBaseUrl;
312
- const cleanUrl = baseUrl.replace(/\/$/, '');
313
- await git.addConfig(`credential.${cleanUrl}.helper`, '');
314
299
  results.providers.gitea.pushed = false;
315
300
  results.providers.gitea.pushError = pushErr.message;
316
301
  results.traceability.errors.push({
@@ -1,4 +1,3 @@
1
- import simpleGit from 'simple-git';
2
1
  import { MCPError } from '../utils/errors.js';
3
2
  import { normalizeToolParams } from '../utils/repoHelpers.js';
4
3
  import axios from 'axios';
@@ -123,7 +122,6 @@ export class GitWorkflowTool {
123
122
  if (!action)
124
123
  throw new MCPError('VALIDATION_ERROR', 'action is required. You can use either "action" or "command" parameter.');
125
124
  const projectPath = params.projectPath;
126
- const git = projectPath ? simpleGit({ baseDir: projectPath }) : null;
127
125
  // Validação de path para segurança
128
126
  if (projectPath) {
129
127
  const path = await import('path');
@@ -133,21 +131,24 @@ export class GitWorkflowTool {
133
131
  throw new MCPError('VALIDATION_ERROR', 'Invalid project path provided');
134
132
  }
135
133
  }
134
+ if (!ctx.gitAdapter) {
135
+ throw new MCPError('INTERNAL_ERROR', 'Git adapter not available');
136
+ }
136
137
  switch (action) {
137
138
  case 'init': {
138
- if (!projectPath || !git)
139
+ if (!projectPath)
139
140
  throw new MCPError('VALIDATION_ERROR', 'projectPath is required for init');
140
- await git.init(params.bare || false);
141
+ await ctx.gitAdapter.init(projectPath);
141
142
  return { success: true, path: projectPath };
142
143
  }
143
144
  case 'status': {
144
- if (!projectPath || !git)
145
+ if (!projectPath)
145
146
  throw new MCPError('VALIDATION_ERROR', 'projectPath is required for status');
146
- const status = await git.status();
147
+ const status = await ctx.gitAdapter.status(projectPath);
147
148
  return { success: true, status };
148
149
  }
149
150
  case 'commit': {
150
- if (!projectPath || !git)
151
+ if (!projectPath)
151
152
  throw new MCPError('VALIDATION_ERROR', 'projectPath is required for commit');
152
153
  const message = params.message;
153
154
  if (!message)
@@ -162,40 +163,26 @@ export class GitWorkflowTool {
162
163
  files = [files];
163
164
  }
164
165
  try {
165
- await git.add(files);
166
+ await ctx.gitAdapter.add(projectPath, files);
166
167
  }
167
168
  catch (addErr) {
168
- // If add fails with pathspec error, try adding files individually
169
- if (addErr.message.includes('pathspec') && files.length > 1) {
170
- for (const file of files) {
171
- try {
172
- await git.add(file);
173
- }
174
- catch (singleErr) {
175
- console.warn(`Failed to add ${file}: ${singleErr.message}`);
176
- }
177
- }
178
- }
179
- else {
180
- throw addErr;
181
- }
169
+ console.warn(`Failed to add files: ${addErr.message}`);
170
+ throw addErr;
182
171
  }
183
- const result = await git.commit(message);
172
+ const result = await ctx.gitAdapter.commit(projectPath, message);
184
173
  return { success: true, commit: result };
185
174
  }
186
175
  case 'sync': {
187
- if (!projectPath || !git)
176
+ if (!projectPath)
188
177
  throw new MCPError('VALIDATION_ERROR', 'projectPath is required for sync');
189
178
  const remote = params.remote || 'origin';
190
- const branch = params.branch;
191
- await git.fetch(remote);
192
- if (branch) {
193
- await git.pull(remote, branch);
179
+ let branch = params.branch;
180
+ if (!branch) {
181
+ branch = await ctx.gitAdapter.getCurrentBranch(projectPath);
194
182
  }
195
- else {
196
- await git.pull();
197
- }
198
- await git.push();
183
+ await ctx.gitAdapter.fetch(projectPath, remote);
184
+ await ctx.gitAdapter.pull(projectPath, remote, branch);
185
+ await ctx.gitAdapter.push(projectPath, remote, branch);
199
186
  return { success: true, message: 'Repository synced' };
200
187
  }
201
188
  case 'backup': {
@@ -445,10 +432,10 @@ export class GitWorkflowTool {
445
432
  return results;
446
433
  }
447
434
  case 'push': {
448
- if (!projectPath || !git)
435
+ if (!projectPath)
449
436
  throw new MCPError('VALIDATION_ERROR', 'projectPath is required for push');
450
437
  const remote = params.remote || 'origin';
451
- const branch = params.branch;
438
+ let branch = params.branch;
452
439
  const force = params.force || false;
453
440
  const setUpstream = params.setUpstream !== undefined ? params.setUpstream : true;
454
441
  // Segurança: verificar confirmação para operações destrutivas
@@ -460,24 +447,18 @@ export class GitWorkflowTool {
460
447
  'If you must force push, ensure you understand the consequences.',
461
448
  ]);
462
449
  }
463
- const pushOptions = [];
464
- if (force)
465
- pushOptions.push('--force');
466
- if (setUpstream)
467
- pushOptions.push('--set-upstream');
468
450
  try {
469
- const pushResult = await git.push(remote, branch, pushOptions);
451
+ if (!branch) {
452
+ branch = await ctx.gitAdapter.getCurrentBranch(projectPath);
453
+ }
454
+ await ctx.gitAdapter.push(projectPath, remote, branch, force, setUpstream);
470
455
  return {
471
456
  success: true,
472
457
  remote,
473
- branch: branch || 'current',
458
+ branch,
474
459
  force,
475
460
  setUpstream,
476
- result: {
477
- pushed: pushResult.pushed || [],
478
- remoteMessages: pushResult.remoteMessages?.all || [],
479
- update: pushResult.update,
480
- },
461
+ message: 'Push successful'
481
462
  };
482
463
  }
483
464
  catch (err) {
@@ -251,6 +251,15 @@ export class IsomorphicGitAdapter {
251
251
  // ========================================================================
252
252
  async commit(dir, message, providedAuthor) {
253
253
  const author = await this.getAuthor(dir, providedAuthor);
254
+ // Check if HEAD exists to determine if this is the initial commit
255
+ let parent;
256
+ try {
257
+ await git.resolveRef({ fs, dir, ref: 'HEAD' });
258
+ }
259
+ catch (err) {
260
+ // HEAD not found, this is the first commit
261
+ parent = [];
262
+ }
254
263
  const sha = await git.commit({
255
264
  fs,
256
265
  dir,
@@ -261,6 +270,7 @@ export class IsomorphicGitAdapter {
261
270
  timestamp: author.timestamp,
262
271
  timezoneOffset: author.timezoneOffset,
263
272
  },
273
+ parent,
264
274
  });
265
275
  return sha;
266
276
  }
@@ -28,12 +28,7 @@ export function getGiteaOwner() {
28
28
  * Get Gitea base URL from environment
29
29
  */
30
30
  export function getGiteaUrl() {
31
- const raw = process.env.GITEA_URL;
32
- if (!raw)
33
- return undefined;
34
- // Trim whitespace and strip accidental surrounding quotes/backticks
35
- const trimmed = raw.trim().replace(/^['"`]+|['"`]+$/g, '');
36
- return trimmed;
31
+ return process.env.GITEA_URL;
37
32
  }
38
33
  /**
39
34
  * Build GitHub repository URL
@@ -50,12 +45,12 @@ export function buildGiteaUrl(owner, repo) {
50
45
  if (!baseUrl) {
51
46
  throw new Error('GITEA_URL not configured');
52
47
  }
53
- // Normalize base URL (remove trailing slashes)
54
- const normalized = baseUrl.replace(/\/$/, '');
55
- // Preserve protocol and embed credentials only if token is provided
56
- const hasToken = Boolean(token);
57
- const withCreds = hasToken ? normalized.replace(/^https?:\/\//, (proto) => `${proto}${owner}:${token}@`) : normalized;
58
- return `${withCreds}/${owner}/${repo}.git`;
48
+ if (!token) {
49
+ throw new Error('GITEA_TOKEN not configured');
50
+ }
51
+ // Remove protocol from baseUrl
52
+ const urlWithoutProtocol = baseUrl.replace(/^https?:\/\//, '');
53
+ return `http://${owner}:${token}@${urlWithoutProtocol}/${owner}/${repo}.git`;
59
54
  }
60
55
  /**
61
56
  * Get repository info from project path and environment
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@andrebuzeli/git-mcp",
3
- "version": "10.0.7",
3
+ "version": "10.0.9",
4
4
  "type": "module",
5
5
  "description": "Professional MCP server for Git operations - STDIO UNIVERSAL: works in ANY IDE (Cursor, VSCode, Claude Desktop, Trae AI, Kiro.dev). Fully autonomous DUAL execution (GitHub + Gitea APIs) with automatic username detection. Smart parameter normalization handles both 'action' and 'command' formats. All tools execute on BOTH providers simultaneously. No manual parameters needed.",
6
6
  "main": "dist/index.js",
@@ -59,8 +59,7 @@
59
59
  "axios": "^1.6.0",
60
60
  "body-parser": "^1.20.2",
61
61
  "express": "^4.18.2",
62
- "isomorphic-git": "^1.34.0",
63
- "simple-git": "^3.20.0"
62
+ "isomorphic-git": "^1.34.0"
64
63
  },
65
64
  "devDependencies": {
66
65
  "@semantic-release/commit-analyzer": "^10.0.0",
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};
@@ -1,224 +0,0 @@
1
- #!/usr/bin/env node
2
- import { Server } from '@modelcontextprotocol/sdk/server/index.js';
3
- import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
4
- import { CallToolRequestSchema, ListToolsRequestSchema, ListResourcesRequestSchema, ReadResourceRequestSchema, ListPromptsRequestSchema, GetPromptRequestSchema, } from '@modelcontextprotocol/sdk/types.js';
5
- import { ProviderManager } from './providers/providerManager.js';
6
- import { MCPError } from './utils/errors.js';
7
- import { IsomorphicGitAdapter } from './utils/gitAdapter.js';
8
- import { GitFilesTool } from './tools/gitFiles.js';
9
- import { GitWorkflowTool } from './tools/gitWorkflow.js';
10
- import { GitBranchesTool } from './tools/gitBranches.js';
11
- import { GitIssuesTool } from './tools/gitIssues.js';
12
- import { GitPullsTool } from './tools/gitPulls.js';
13
- import { GitTagsTool } from './tools/gitTags.js';
14
- import { GitReleaseTool } from './tools/gitRelease.js';
15
- import { GitRemoteTool } from './tools/gitRemote.js';
16
- import { GitResetTool } from './tools/gitReset.js';
17
- import { GitStashTool } from './tools/gitStash.js';
18
- import { GitConfigTool } from './tools/gitConfig.js';
19
- import { GitMonitorTool } from './tools/gitMonitor.js';
20
- import { GitBackupTool } from './tools/gitBackup.js';
21
- import { GitArchiveTool } from './tools/gitArchive.js';
22
- import { GitSyncTool } from './tools/gitSync.js';
23
- import { GitPackagesTool } from './tools/gitPackages.js';
24
- import { GitAnalyticsTool } from './tools/gitAnalytics.js';
25
- import { GitUploadTool } from './tools/gitUpload.js';
26
- import { GitUpdateTool } from './tools/gitUpdate.js';
27
- import { GitHistoryTool } from './tools/gitHistory.js';
28
- import { GitFixTool } from './tools/gitFix.tool.js';
29
- import { GitIgnoreTool } from './tools/gitIgnore.js';
30
- import { GIT_PROMPTS } from './prompts/gitPrompts.js';
31
- import TOOLS_GUIDE from './resources/toolsGuide.js';
32
- import { Logger, logToolExecution, logSecurityEvent, logPerformanceMetric } from './utils/logger.js';
33
- async function main() {
34
- const providerManager = new ProviderManager();
35
- const gitAdapter = new IsomorphicGitAdapter(providerManager);
36
- const logger = Logger.getInstance();
37
- // Skip validation on startup to prevent hanging (validation happens on first use)
38
- // Provider validation moved to lazy initialization
39
- // Register all 22 Git tools
40
- const tools = [
41
- new GitWorkflowTool(),
42
- new GitFilesTool(),
43
- new GitBranchesTool(),
44
- new GitIssuesTool(),
45
- new GitPullsTool(),
46
- new GitTagsTool(),
47
- new GitReleaseTool(),
48
- new GitRemoteTool(),
49
- new GitResetTool(),
50
- new GitStashTool(),
51
- new GitConfigTool(),
52
- new GitMonitorTool(),
53
- new GitBackupTool(),
54
- new GitArchiveTool(),
55
- new GitSyncTool(),
56
- new GitPackagesTool(),
57
- new GitAnalyticsTool(),
58
- new GitUploadTool(),
59
- new GitUpdateTool(),
60
- new GitHistoryTool(),
61
- new GitFixTool(),
62
- new GitIgnoreTool(),
63
- ];
64
- // Register resources
65
- const resources = [
66
- TOOLS_GUIDE
67
- ];
68
- // Silent mode for MCP clients - only log to stderr in debug mode
69
- if (process.env.DEBUG) {
70
- logger.info(`Registered ${tools.length} Git tools`);
71
- logger.info(`Registered ${resources.length} resource(s)`);
72
- logger.info(`Registered ${GIT_PROMPTS.length} prompt(s)`);
73
- }
74
- // Create MCP Server with STDIO transport
75
- const server = new Server({
76
- name: '@andrebuzeli/git-mcp',
77
- version: '10.0.4',
78
- });
79
- // Register tool list handler
80
- server.setRequestHandler(ListToolsRequestSchema, async () => {
81
- logger.debug('Listing available tools');
82
- return {
83
- tools: tools.map(tool => ({
84
- name: tool.name,
85
- description: tool.description,
86
- inputSchema: tool.inputSchema || {
87
- type: 'object',
88
- properties: {},
89
- additionalProperties: true,
90
- },
91
- })),
92
- };
93
- });
94
- // Register tool execution handler with logging e segurança aprimorados
95
- server.setRequestHandler(CallToolRequestSchema, async (request) => {
96
- const toolName = request.params.name;
97
- const tool = tools.find(t => t.name === toolName);
98
- const startTime = Date.now();
99
- if (!tool) {
100
- logSecurityEvent('TOOL_NOT_FOUND', { toolName });
101
- throw new Error(`Tool not found: ${toolName}`);
102
- }
103
- try {
104
- // Validar argumentos contra o schema da ferramenta
105
- const args = request.params.arguments ?? {};
106
- // Validação básica de segurança
107
- if (typeof args !== 'object' || args === null) {
108
- logSecurityEvent('INVALID_ARGUMENTS', { toolName, args });
109
- throw new MCPError('VALIDATION_ERROR', 'Arguments must be an object');
110
- }
111
- // Validar projectPath se existir
112
- if ('projectPath' in args) {
113
- const projectPath = args.projectPath;
114
- if (typeof projectPath !== 'string') {
115
- logSecurityEvent('INVALID_PROJECT_PATH_TYPE', { toolName, projectPath });
116
- throw new MCPError('VALIDATION_ERROR', 'projectPath must be a string');
117
- }
118
- if (!projectPath || projectPath.includes('..')) {
119
- logSecurityEvent('PATH_TRAVERSION_ATTEMPT', { toolName, projectPath });
120
- throw new MCPError('VALIDATION_ERROR', 'Invalid project path');
121
- }
122
- }
123
- // Log da execução
124
- logger.info(`Executing tool: ${toolName}`, { args }, toolName);
125
- // Executar a ferramenta
126
- const result = await tool.handle(args, { providerManager, gitAdapter });
127
- const duration = Date.now() - startTime;
128
- logPerformanceMetric(`tool_${toolName}`, duration, { success: true });
129
- logToolExecution(toolName, args, result);
130
- return {
131
- content: [
132
- {
133
- type: 'text',
134
- text: typeof result === 'string' ? result : JSON.stringify(result, null, 2),
135
- },
136
- ],
137
- };
138
- }
139
- catch (error) {
140
- const duration = Date.now() - startTime;
141
- logPerformanceMetric(`tool_${toolName}`, duration, { success: false, error: error.message });
142
- logToolExecution(toolName, request.params.arguments ?? {}, undefined, error);
143
- return {
144
- content: [
145
- {
146
- type: 'text',
147
- text: `Error: ${error.message || String(error)}`,
148
- },
149
- ],
150
- isError: true,
151
- };
152
- }
153
- });
154
- // Register resource list handler
155
- server.setRequestHandler(ListResourcesRequestSchema, async () => {
156
- logger.debug('Listing available resources');
157
- return {
158
- resources: resources.map(resource => ({
159
- uri: resource.uri,
160
- name: resource.name,
161
- description: resource.description,
162
- mimeType: resource.mimeType,
163
- })),
164
- };
165
- });
166
- // Register resource read handler
167
- server.setRequestHandler(ReadResourceRequestSchema, async (request) => {
168
- const uri = request.params.uri;
169
- const resource = resources.find(r => r.uri === uri);
170
- if (!resource) {
171
- logger.warn(`Resource not found: ${uri}`);
172
- throw new Error(`Resource not found: ${uri}`);
173
- }
174
- logger.debug(`Reading resource: ${uri}`);
175
- return {
176
- contents: [
177
- {
178
- uri: resource.uri,
179
- mimeType: resource.mimeType,
180
- text: resource.content,
181
- },
182
- ],
183
- };
184
- });
185
- // Register prompt list handler
186
- server.setRequestHandler(ListPromptsRequestSchema, async () => {
187
- logger.debug('Listing available prompts');
188
- return {
189
- prompts: GIT_PROMPTS.map(prompt => ({
190
- name: prompt.name,
191
- description: prompt.description,
192
- arguments: prompt.arguments,
193
- })),
194
- };
195
- });
196
- // Register prompt get handler
197
- server.setRequestHandler(GetPromptRequestSchema, async (request) => {
198
- const promptName = request.params.name;
199
- const prompt = GIT_PROMPTS.find(p => p.name === promptName);
200
- if (!prompt) {
201
- logger.warn(`Prompt not found: ${promptName}`);
202
- throw new Error(`Prompt not found: ${promptName}`);
203
- }
204
- logger.debug(`Generating prompt: ${promptName}`);
205
- const result = await prompt.generate(request.params.arguments ?? {}, { providerManager, gitAdapter });
206
- return {
207
- description: result.description,
208
- messages: result.messages,
209
- };
210
- });
211
- const transport = new StdioServerTransport();
212
- await server.connect(transport);
213
- // Only log in debug mode
214
- if (process.env.DEBUG) {
215
- logger.info(`✅ git-mcp MCP server running via STDIO`);
216
- logger.info(`Tools: ${tools.length} registered`);
217
- logger.info(`Resources: ${resources.length} registered`);
218
- logger.info(`Prompts: ${GIT_PROMPTS.length} registered`);
219
- }
220
- }
221
- main().catch(err => {
222
- console.error('❌ Failed to start git-mcp:', err);
223
- process.exit(1);
224
- });