@andrebuzeli/git-mcp 6.3.2 → 7.0.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.
@@ -10,11 +10,31 @@ exports.TOOLS_GUIDE = {
10
10
  name: "Git-MCP Tools Complete Guide",
11
11
  description: "Detailed documentation and usage instructions for all 20 Git tools with 102 actions",
12
12
  mimeType: "text/markdown",
13
- content: `# 🛠️ Git-MCP v6.3.1 - Complete Tools Guide
13
+ content: `# 🛠️ Git-MCP v7.0.0 - Complete Tools Guide
14
14
 
15
15
  > **Comprehensive documentation for all 21 Git tools with 110+ actions**
16
- > **Supports:** GitHub, Gitea, and Local Git operations
17
- > **NEW in v6.3.1:** git-fix tool for dual-provider migration
16
+ > **Supports:** Automatic DUAL execution on GitHub + Gitea APIs
17
+ > **BREAKING CHANGES in v7.0.0:** ALL tools now execute DUAL automatically with env var usernames
18
+
19
+ ---
20
+
21
+ ## 🚀 v7.0.0 MAJOR UPDATE: AUTOMATIC DUAL EXECUTION
22
+
23
+ **ALL TOOLS NOW:**
24
+ - ✅ Execute on BOTH GitHub and Gitea APIs automatically
25
+ - ✅ Use GITHUB_USERNAME and GITEA_USERNAME from environment variables
26
+ - ✅ Return separated responses: \`{ github: {...}, gitea: {...} }\`
27
+ - ✅ NO manual owner/user/author parameters needed
28
+ - ✅ Each provider uses its own credentials automatically
29
+
30
+ ### Environment Variables Required:
31
+ \\\`\\\`\\\`bash
32
+ GITHUB_TOKEN=your_github_token
33
+ GITHUB_USERNAME=YourGitHubUsername
34
+ GITEA_TOKEN=your_gitea_token
35
+ GITEA_USERNAME=yourGiteaUsername
36
+ GITEA_URL=http://your-gitea-server:port
37
+ \\\`\\\`\\\`
18
38
 
19
39
  ---
20
40
 
@@ -2,28 +2,5 @@ import { Tool, MCPContext } from '../types';
2
2
  export declare class GitAnalyticsTool implements Tool {
3
3
  name: string;
4
4
  description: string;
5
- handle(params: Record<string, any>, ctx: MCPContext): Promise<{
6
- success: boolean;
7
- stats: {
8
- totalCommits: number;
9
- branches: number;
10
- tags: number;
11
- latestCommit: (import("simple-git").DefaultLogFields & import("simple-git").ListLogLine) | null;
12
- };
13
- commits?: undefined;
14
- details?: undefined;
15
- contributors?: undefined;
16
- } | {
17
- success: boolean;
18
- commits: number;
19
- details: readonly (import("simple-git").DefaultLogFields & import("simple-git").ListLogLine)[];
20
- stats?: undefined;
21
- contributors?: undefined;
22
- } | {
23
- success: boolean;
24
- contributors: any[];
25
- stats?: undefined;
26
- commits?: undefined;
27
- details?: undefined;
28
- }>;
5
+ handle(params: Record<string, any>, ctx: MCPContext): Promise<any>;
29
6
  }
@@ -4,65 +4,190 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.GitAnalyticsTool = void 0;
7
- const simple_git_1 = __importDefault(require("simple-git"));
8
7
  const errors_1 = require("../utils/errors");
8
+ const axios_1 = __importDefault(require("axios"));
9
+ const repoHelpers_1 = require("../utils/repoHelpers");
9
10
  class GitAnalyticsTool {
10
11
  constructor() {
11
12
  this.name = 'git-analytics';
12
- this.description = 'Repository analytics and statistics';
13
+ this.description = 'Repository analytics and statistics - automatic dual-provider execution using GitHub and Gitea APIs';
13
14
  }
14
15
  async handle(params, ctx) {
15
16
  const action = params.action;
16
17
  const projectPath = params.projectPath;
17
- if (!action || !projectPath) {
18
- throw new errors_1.MCPError('VALIDATION_ERROR', 'action and projectPath are required');
19
- }
20
- const git = (0, simple_git_1.default)({ baseDir: projectPath });
18
+ if (!action)
19
+ throw new errors_1.MCPError('VALIDATION_ERROR', 'action is required');
20
+ if (!projectPath)
21
+ throw new errors_1.MCPError('VALIDATION_ERROR', 'projectPath is required');
22
+ // Auto-extract repo info from projectPath
23
+ const repoInfo = (0, repoHelpers_1.getRepoInfo)(projectPath);
24
+ const repo = repoInfo.repoName;
25
+ // Each provider uses its own username from env
26
+ const githubOwner = process.env.GITHUB_USERNAME;
27
+ const giteaOwner = process.env.GITEA_USERNAME;
21
28
  switch (action) {
22
29
  case 'stats': {
23
- const log = await git.log();
24
- const branches = await git.branch();
25
- const tags = await git.tags();
26
- return {
27
- success: true,
28
- stats: {
29
- totalCommits: log.total,
30
- branches: branches.all.length,
31
- tags: tags.all.length,
32
- latestCommit: log.latest,
33
- },
34
- };
30
+ const results = { success: true, providers: {} };
31
+ // GitHub API
32
+ if (ctx.providerManager.github && githubOwner) {
33
+ try {
34
+ const repoData = await ctx.providerManager.github.rest.repos.get({
35
+ owner: githubOwner,
36
+ repo: repo,
37
+ });
38
+ const branches = await ctx.providerManager.github.rest.repos.listBranches({
39
+ owner: githubOwner,
40
+ repo: repo,
41
+ });
42
+ const tags = await ctx.providerManager.github.rest.repos.listTags({
43
+ owner: githubOwner,
44
+ repo: repo,
45
+ });
46
+ const commits = await ctx.providerManager.github.rest.repos.listCommits({
47
+ owner: githubOwner,
48
+ repo: repo,
49
+ per_page: 1,
50
+ });
51
+ results.providers.github = {
52
+ success: true,
53
+ stats: {
54
+ totalCommits: repoData.data.size,
55
+ branches: branches.data.length,
56
+ tags: tags.data.length,
57
+ defaultBranch: repoData.data.default_branch,
58
+ latestCommit: commits.data[0] || null,
59
+ size: repoData.data.size,
60
+ language: repoData.data.language,
61
+ stars: repoData.data.stargazers_count,
62
+ forks: repoData.data.forks_count,
63
+ openIssues: repoData.data.open_issues_count,
64
+ },
65
+ };
66
+ }
67
+ catch (err) {
68
+ results.providers.github = { success: false, error: err.message };
69
+ }
70
+ }
71
+ // Gitea API
72
+ if (ctx.providerManager.giteaBaseUrl && giteaOwner) {
73
+ try {
74
+ const repoData = await axios_1.default.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repo}`, { headers: { Authorization: `token ${ctx.providerManager.giteaToken}` } });
75
+ const branches = await axios_1.default.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repo}/branches`, { headers: { Authorization: `token ${ctx.providerManager.giteaToken}` } });
76
+ const tags = await axios_1.default.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repo}/tags`, { headers: { Authorization: `token ${ctx.providerManager.giteaToken}` } });
77
+ const commits = await axios_1.default.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repo}/commits`, {
78
+ params: { limit: 1 },
79
+ headers: { Authorization: `token ${ctx.providerManager.giteaToken}` }
80
+ });
81
+ results.providers.gitea = {
82
+ success: true,
83
+ stats: {
84
+ totalCommits: repoData.data.size,
85
+ branches: branches.data.length,
86
+ tags: tags.data.length,
87
+ defaultBranch: repoData.data.default_branch,
88
+ latestCommit: commits.data[0] || null,
89
+ size: repoData.data.size,
90
+ language: repoData.data.language,
91
+ stars: repoData.data.stars_count,
92
+ forks: repoData.data.forks_count,
93
+ openIssues: repoData.data.open_issues_count,
94
+ },
95
+ };
96
+ }
97
+ catch (err) {
98
+ results.providers.gitea = { success: false, error: err.message };
99
+ }
100
+ }
101
+ return results;
35
102
  }
36
103
  case 'commits': {
37
- const since = params.since;
38
- const until = params.until;
39
- const log = await git.log({
40
- from: since,
41
- to: until,
42
- maxCount: params.limit || 1000,
43
- });
44
- return {
45
- success: true,
46
- commits: log.all.length,
47
- details: log.all,
48
- };
104
+ const results = { success: true, providers: {} };
105
+ // GitHub API
106
+ if (ctx.providerManager.github && githubOwner) {
107
+ try {
108
+ const commits = await ctx.providerManager.github.rest.repos.listCommits({
109
+ owner: githubOwner,
110
+ repo: repo,
111
+ since: params.since,
112
+ until: params.until,
113
+ per_page: params.limit || 100,
114
+ });
115
+ results.providers.github = {
116
+ success: true,
117
+ commits: commits.data.length,
118
+ details: commits.data,
119
+ };
120
+ }
121
+ catch (err) {
122
+ results.providers.github = { success: false, error: err.message };
123
+ }
124
+ }
125
+ // Gitea API
126
+ if (ctx.providerManager.giteaBaseUrl && giteaOwner) {
127
+ try {
128
+ const commits = await axios_1.default.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repo}/commits`, {
129
+ params: {
130
+ since: params.since,
131
+ until: params.until,
132
+ limit: params.limit || 100,
133
+ },
134
+ headers: { Authorization: `token ${ctx.providerManager.giteaToken}` }
135
+ });
136
+ results.providers.gitea = {
137
+ success: true,
138
+ commits: commits.data.length,
139
+ details: commits.data,
140
+ };
141
+ }
142
+ catch (err) {
143
+ results.providers.gitea = { success: false, error: err.message };
144
+ }
145
+ }
146
+ return results;
49
147
  }
50
148
  case 'contributors': {
51
- const log = await git.log();
52
- const contributors = new Map();
53
- log.all.forEach(commit => {
54
- const author = commit.author_name;
55
- if (contributors.has(author)) {
56
- contributors.get(author).count++;
149
+ const results = { success: true, providers: {} };
150
+ // GitHub API
151
+ if (ctx.providerManager.github && githubOwner) {
152
+ try {
153
+ const contributors = await ctx.providerManager.github.rest.repos.listContributors({
154
+ owner: githubOwner,
155
+ repo: repo,
156
+ per_page: 100,
157
+ });
158
+ results.providers.github = {
159
+ success: true,
160
+ contributors: contributors.data.map((c) => ({
161
+ login: c.login,
162
+ name: c.login,
163
+ contributions: c.contributions,
164
+ avatar_url: c.avatar_url,
165
+ })),
166
+ };
167
+ }
168
+ catch (err) {
169
+ results.providers.github = { success: false, error: err.message };
170
+ }
171
+ }
172
+ // Gitea API
173
+ if (ctx.providerManager.giteaBaseUrl && giteaOwner) {
174
+ try {
175
+ const contributors = await axios_1.default.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repo}/contributors`, { headers: { Authorization: `token ${ctx.providerManager.giteaToken}` } });
176
+ results.providers.gitea = {
177
+ success: true,
178
+ contributors: contributors.data.map((c) => ({
179
+ login: c.login,
180
+ name: c.name || c.login,
181
+ contributions: c.contributions,
182
+ avatar_url: c.avatar_url,
183
+ })),
184
+ };
57
185
  }
58
- else {
59
- contributors.set(author, { name: author, email: commit.author_email, count: 1 });
186
+ catch (err) {
187
+ results.providers.gitea = { success: false, error: err.message };
60
188
  }
61
- });
62
- return {
63
- success: true,
64
- contributors: Array.from(contributors.values()).sort((a, b) => b.count - a.count),
65
- };
189
+ }
190
+ return results;
66
191
  }
67
192
  default:
68
193
  throw new errors_1.MCPError('VALIDATION_ERROR', `Unsupported action: ${action}`);
@@ -2,71 +2,5 @@ import { Tool, MCPContext } from '../types';
2
2
  export declare class GitBranchesTool implements Tool {
3
3
  name: string;
4
4
  description: string;
5
- handle(params: Record<string, any>, ctx: MCPContext): Promise<{
6
- success: boolean;
7
- branch: any;
8
- branches?: undefined;
9
- current?: undefined;
10
- deleted?: undefined;
11
- result?: undefined;
12
- baseBranch?: undefined;
13
- compareBranch?: undefined;
14
- commits?: undefined;
15
- diff?: undefined;
16
- } | {
17
- branches: string[];
18
- current: string;
19
- success?: undefined;
20
- branch?: undefined;
21
- deleted?: undefined;
22
- result?: undefined;
23
- baseBranch?: undefined;
24
- compareBranch?: undefined;
25
- commits?: undefined;
26
- diff?: undefined;
27
- } | {
28
- success: boolean;
29
- branch: string;
30
- current: boolean;
31
- branches?: undefined;
32
- deleted?: undefined;
33
- result?: undefined;
34
- baseBranch?: undefined;
35
- compareBranch?: undefined;
36
- commits?: undefined;
37
- diff?: undefined;
38
- } | {
39
- success: boolean;
40
- deleted: any;
41
- branch?: undefined;
42
- branches?: undefined;
43
- current?: undefined;
44
- result?: undefined;
45
- baseBranch?: undefined;
46
- compareBranch?: undefined;
47
- commits?: undefined;
48
- diff?: undefined;
49
- } | {
50
- success: boolean;
51
- result: import("simple-git").MergeResult;
52
- branch?: undefined;
53
- branches?: undefined;
54
- current?: undefined;
55
- deleted?: undefined;
56
- baseBranch?: undefined;
57
- compareBranch?: undefined;
58
- commits?: undefined;
59
- diff?: undefined;
60
- } | {
61
- success: boolean;
62
- baseBranch: any;
63
- compareBranch: any;
64
- commits: readonly (import("simple-git").DefaultLogFields & import("simple-git").ListLogLine)[];
65
- diff: string;
66
- branch?: undefined;
67
- branches?: undefined;
68
- current?: undefined;
69
- deleted?: undefined;
70
- result?: undefined;
71
- }>;
5
+ handle(params: Record<string, any>, ctx: MCPContext): Promise<any>;
72
6
  }
@@ -7,10 +7,12 @@ exports.GitBranchesTool = void 0;
7
7
  const simple_git_1 = __importDefault(require("simple-git"));
8
8
  const errors_1 = require("../utils/errors");
9
9
  const safetyController_1 = require("../utils/safetyController");
10
+ const repoHelpers_1 = require("../utils/repoHelpers");
11
+ const axios_1 = __importDefault(require("axios"));
10
12
  class GitBranchesTool {
11
13
  constructor() {
12
14
  this.name = 'git-branches';
13
- this.description = 'Branch management operations';
15
+ this.description = 'Branch management operations - automatic dual-provider execution for remote queries';
14
16
  }
15
17
  async handle(params, ctx) {
16
18
  const action = params.action;
@@ -32,14 +34,60 @@ class GitBranchesTool {
32
34
  if (params.checkout !== false) {
33
35
  await git.checkout(branchName);
34
36
  }
35
- return { success: true, branch: branchName };
37
+ return { success: true, branch: branchName, local: true };
36
38
  }
37
39
  case 'list': {
38
- const branches = await git.branch();
39
- return {
40
- branches: branches.all,
41
- current: branches.current,
40
+ const localBranches = await git.branch();
41
+ const results = {
42
+ success: true,
43
+ local: {
44
+ branches: localBranches.all,
45
+ current: localBranches.current,
46
+ },
47
+ providers: {}
42
48
  };
49
+ // Also query remote APIs
50
+ const repoInfo = (0, repoHelpers_1.getRepoInfo)(projectPath);
51
+ const githubOwner = process.env.GITHUB_USERNAME;
52
+ const giteaOwner = process.env.GITEA_USERNAME;
53
+ // GitHub
54
+ if (ctx.providerManager.github && githubOwner) {
55
+ try {
56
+ const branches = await ctx.providerManager.github.rest.repos.listBranches({
57
+ owner: githubOwner,
58
+ repo: repoInfo.repoName,
59
+ });
60
+ results.providers.github = {
61
+ success: true,
62
+ branches: branches.data.map((b) => ({
63
+ name: b.name,
64
+ protected: b.protected,
65
+ sha: b.commit.sha,
66
+ })),
67
+ };
68
+ }
69
+ catch (err) {
70
+ results.providers.github = { success: false, error: err.message };
71
+ }
72
+ }
73
+ // Gitea
74
+ if (ctx.providerManager.giteaBaseUrl && giteaOwner) {
75
+ try {
76
+ const branches = await axios_1.default.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repoInfo.repoName}/branches`, { headers: { Authorization: `token ${ctx.providerManager.giteaToken}` } });
77
+ results.providers.gitea = {
78
+ success: true,
79
+ branches: branches.data.map((b) => ({
80
+ name: b.name,
81
+ protected: b.protected,
82
+ sha: b.commit.id,
83
+ })),
84
+ };
85
+ }
86
+ catch (err) {
87
+ results.providers.gitea = { success: false, error: err.message };
88
+ }
89
+ }
90
+ return results;
43
91
  }
44
92
  case 'get': {
45
93
  const branchName = params.branchName;
@@ -47,13 +95,56 @@ class GitBranchesTool {
47
95
  throw new errors_1.MCPError('VALIDATION_ERROR', 'branchName is required');
48
96
  const branches = await git.branch();
49
97
  const branch = branches.all.find(b => b === branchName);
50
- if (!branch) {
51
- throw new errors_1.MCPError('BRANCH_NOT_FOUND', `Branch '${branchName}' not found`, [
52
- 'Check if the branch name is correct',
53
- 'Use git branch -a to see all available branches',
54
- ]);
98
+ const results = {
99
+ success: true,
100
+ local: branch ? { branch, current: branches.current === branchName } : { found: false },
101
+ providers: {}
102
+ };
103
+ // Also query remote APIs
104
+ const repoInfo = (0, repoHelpers_1.getRepoInfo)(projectPath);
105
+ const githubOwner = process.env.GITHUB_USERNAME;
106
+ const giteaOwner = process.env.GITEA_USERNAME;
107
+ // GitHub
108
+ if (ctx.providerManager.github && githubOwner) {
109
+ try {
110
+ const branchData = await ctx.providerManager.github.rest.repos.getBranch({
111
+ owner: githubOwner,
112
+ repo: repoInfo.repoName,
113
+ branch: branchName,
114
+ });
115
+ results.providers.github = {
116
+ success: true,
117
+ branch: {
118
+ name: branchData.data.name,
119
+ protected: branchData.data.protected,
120
+ sha: branchData.data.commit.sha,
121
+ commit: branchData.data.commit,
122
+ },
123
+ };
124
+ }
125
+ catch (err) {
126
+ results.providers.github = { success: false, error: err.message };
127
+ }
128
+ }
129
+ // Gitea
130
+ if (ctx.providerManager.giteaBaseUrl && giteaOwner) {
131
+ try {
132
+ const branchData = await axios_1.default.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repoInfo.repoName}/branches/${branchName}`, { headers: { Authorization: `token ${ctx.providerManager.giteaToken}` } });
133
+ results.providers.gitea = {
134
+ success: true,
135
+ branch: {
136
+ name: branchData.data.name,
137
+ protected: branchData.data.protected,
138
+ sha: branchData.data.commit.id,
139
+ commit: branchData.data.commit,
140
+ },
141
+ };
142
+ }
143
+ catch (err) {
144
+ results.providers.gitea = { success: false, error: err.message };
145
+ }
55
146
  }
56
- return { success: true, branch, current: branches.current === branchName };
147
+ return results;
57
148
  }
58
149
  case 'delete': {
59
150
  (0, safetyController_1.requireConfirmationIfDestructive)('delete', params);
@@ -61,7 +152,7 @@ class GitBranchesTool {
61
152
  if (!branchName)
62
153
  throw new errors_1.MCPError('VALIDATION_ERROR', 'branchName is required');
63
154
  await git.deleteLocalBranch(branchName, params.force);
64
- return { success: true, deleted: branchName };
155
+ return { success: true, deleted: branchName, local: true };
65
156
  }
66
157
  case 'merge': {
67
158
  const branchName = params.branchName;
@@ -72,7 +163,7 @@ class GitBranchesTool {
72
163
  await git.checkout(targetBranch);
73
164
  }
74
165
  const result = await git.merge([branchName]);
75
- return { success: true, result };
166
+ return { success: true, result, local: true };
76
167
  }
77
168
  case 'compare': {
78
169
  const baseBranch = params.baseBranch;
@@ -82,13 +173,54 @@ class GitBranchesTool {
82
173
  }
83
174
  const diff = await git.diff([`${baseBranch}...${compareBranch}`]);
84
175
  const log = await git.log({ from: baseBranch, to: compareBranch });
85
- return {
176
+ const results = {
86
177
  success: true,
87
- baseBranch,
88
- compareBranch,
89
- commits: log.all,
90
- diff,
178
+ local: {
179
+ baseBranch,
180
+ compareBranch,
181
+ commits: log.all,
182
+ diff,
183
+ },
184
+ providers: {}
91
185
  };
186
+ // Also compare on remote APIs
187
+ const repoInfo = (0, repoHelpers_1.getRepoInfo)(projectPath);
188
+ const githubOwner = process.env.GITHUB_USERNAME;
189
+ const giteaOwner = process.env.GITEA_USERNAME;
190
+ // GitHub
191
+ if (ctx.providerManager.github && githubOwner) {
192
+ try {
193
+ const comparison = await ctx.providerManager.github.rest.repos.compareCommits({
194
+ owner: githubOwner,
195
+ repo: repoInfo.repoName,
196
+ base: baseBranch,
197
+ head: compareBranch,
198
+ });
199
+ results.providers.github = {
200
+ success: true,
201
+ comparison: {
202
+ status: comparison.data.status,
203
+ ahead_by: comparison.data.ahead_by,
204
+ behind_by: comparison.data.behind_by,
205
+ total_commits: comparison.data.total_commits,
206
+ commits: comparison.data.commits.map((c) => ({
207
+ sha: c.sha,
208
+ message: c.commit.message,
209
+ author: c.commit.author.name,
210
+ date: c.commit.author.date,
211
+ })),
212
+ },
213
+ };
214
+ }
215
+ catch (err) {
216
+ results.providers.github = { success: false, error: err.message };
217
+ }
218
+ }
219
+ // Gitea - comparison API might not be available
220
+ if (ctx.providerManager.giteaBaseUrl && giteaOwner) {
221
+ results.providers.gitea = { success: false, error: 'Comparison API not available in Gitea' };
222
+ }
223
+ return results;
92
224
  }
93
225
  default:
94
226
  throw new errors_1.MCPError('VALIDATION_ERROR', `Unsupported action: ${action}`);
@@ -2,50 +2,5 @@ import { Tool, MCPContext } from '../types';
2
2
  export declare class GitMonitorTool implements Tool {
3
3
  name: string;
4
4
  description: string;
5
- handle(params: Record<string, any>, ctx: MCPContext): Promise<{
6
- success: boolean;
7
- commits: readonly (import("simple-git").DefaultLogFields & import("simple-git").ListLogLine)[];
8
- total: number;
9
- status?: undefined;
10
- contributors?: undefined;
11
- } | {
12
- success: boolean;
13
- status: import("simple-git").StatusResult | {
14
- branch: string | null;
15
- ahead: number;
16
- behind: number;
17
- staged: string[];
18
- modified: string[];
19
- deleted: string[];
20
- created: string[];
21
- renamed: import("simple-git").StatusResultRenamed[];
22
- conflicted: string[];
23
- not_added: string[];
24
- };
25
- commits?: undefined;
26
- total?: undefined;
27
- contributors?: undefined;
28
- } | {
29
- success: boolean;
30
- commits: {
31
- hash: string;
32
- date: string;
33
- message: string;
34
- author: string;
35
- email: string;
36
- }[];
37
- total?: undefined;
38
- status?: undefined;
39
- contributors?: undefined;
40
- } | {
41
- success: boolean;
42
- contributors: {
43
- name: string;
44
- email: string;
45
- commits: number;
46
- }[];
47
- commits?: undefined;
48
- total?: undefined;
49
- status?: undefined;
50
- }>;
5
+ handle(params: Record<string, any>, ctx: MCPContext): Promise<any>;
51
6
  }
@@ -6,10 +6,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.GitMonitorTool = void 0;
7
7
  const simple_git_1 = __importDefault(require("simple-git"));
8
8
  const errors_1 = require("../utils/errors");
9
+ const repoHelpers_1 = require("../utils/repoHelpers");
10
+ const axios_1 = __importDefault(require("axios"));
9
11
  class GitMonitorTool {
10
12
  constructor() {
11
13
  this.name = 'git-monitor';
12
- this.description = 'Repository monitoring and status operations';
14
+ this.description = 'Repository monitoring and status operations - automatic dual-provider execution for remote queries';
13
15
  }
14
16
  async handle(params, ctx) {
15
17
  const action = params.action;
@@ -30,11 +32,72 @@ class GitMonitorTool {
30
32
  if (params.author)
31
33
  options.author = params.author;
32
34
  const log = await git.log(options);
33
- return {
35
+ const results = {
34
36
  success: true,
35
- commits: log.all,
36
- total: log.total,
37
+ local: {
38
+ commits: log.all,
39
+ total: log.total,
40
+ },
41
+ providers: {}
37
42
  };
43
+ // Also query remote APIs
44
+ const repoInfo = (0, repoHelpers_1.getRepoInfo)(projectPath);
45
+ const githubOwner = process.env.GITHUB_USERNAME;
46
+ const giteaOwner = process.env.GITEA_USERNAME;
47
+ // GitHub
48
+ if (ctx.providerManager.github && githubOwner) {
49
+ try {
50
+ const commits = await ctx.providerManager.github.rest.repos.listCommits({
51
+ owner: githubOwner,
52
+ repo: repoInfo.repoName,
53
+ since: params.since,
54
+ until: params.until,
55
+ per_page: params.limit || 10,
56
+ });
57
+ results.providers.github = {
58
+ success: true,
59
+ commits: commits.data.map((c) => ({
60
+ sha: c.sha,
61
+ message: c.commit.message,
62
+ author: c.commit.author.name,
63
+ email: c.commit.author.email,
64
+ date: c.commit.author.date,
65
+ url: c.html_url,
66
+ })),
67
+ };
68
+ }
69
+ catch (err) {
70
+ results.providers.github = { success: false, error: err.message };
71
+ }
72
+ }
73
+ // Gitea
74
+ if (ctx.providerManager.giteaBaseUrl && giteaOwner) {
75
+ try {
76
+ const commits = await axios_1.default.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repoInfo.repoName}/commits`, {
77
+ params: {
78
+ since: params.since,
79
+ until: params.until,
80
+ limit: params.limit || 10,
81
+ },
82
+ headers: { Authorization: `token ${ctx.providerManager.giteaToken}` }
83
+ });
84
+ results.providers.gitea = {
85
+ success: true,
86
+ commits: commits.data.map((c) => ({
87
+ sha: c.sha,
88
+ message: c.commit.message,
89
+ author: c.commit.author.name,
90
+ email: c.commit.author.email,
91
+ date: c.commit.author.date,
92
+ url: c.html_url,
93
+ })),
94
+ };
95
+ }
96
+ catch (err) {
97
+ results.providers.gitea = { success: false, error: err.message };
98
+ }
99
+ }
100
+ return results;
38
101
  }
39
102
  case 'status': {
40
103
  const status = await git.status();
@@ -50,7 +113,7 @@ class GitMonitorTool {
50
113
  conflicted: status.conflicted,
51
114
  not_added: status.not_added,
52
115
  } : status;
53
- return { success: true, status: detailed };
116
+ return { success: true, local: { status: detailed } };
54
117
  }
55
118
  case 'commits': {
56
119
  const branch = params.branch;
@@ -64,16 +127,75 @@ class GitMonitorTool {
64
127
  if (params.author)
65
128
  options.author = params.author;
66
129
  const log = await git.log(options);
67
- return {
130
+ const results = {
68
131
  success: true,
69
- commits: log.all.map(c => ({
70
- hash: c.hash,
71
- date: c.date,
72
- message: c.message,
73
- author: c.author_name,
74
- email: c.author_email,
75
- })),
132
+ local: {
133
+ commits: log.all.map(c => ({
134
+ hash: c.hash,
135
+ date: c.date,
136
+ message: c.message,
137
+ author: c.author_name,
138
+ email: c.author_email,
139
+ })),
140
+ },
141
+ providers: {}
76
142
  };
143
+ // Also query remote APIs
144
+ const repoInfo = (0, repoHelpers_1.getRepoInfo)(projectPath);
145
+ const githubOwner = process.env.GITHUB_USERNAME;
146
+ const giteaOwner = process.env.GITEA_USERNAME;
147
+ // GitHub
148
+ if (ctx.providerManager.github && githubOwner) {
149
+ try {
150
+ const commits = await ctx.providerManager.github.rest.repos.listCommits({
151
+ owner: githubOwner,
152
+ repo: repoInfo.repoName,
153
+ sha: branch,
154
+ since: params.since,
155
+ per_page: params.limit || 50,
156
+ });
157
+ results.providers.github = {
158
+ success: true,
159
+ commits: commits.data.map((c) => ({
160
+ sha: c.sha,
161
+ message: c.commit.message,
162
+ author: c.commit.author.name,
163
+ email: c.commit.author.email,
164
+ date: c.commit.author.date,
165
+ })),
166
+ };
167
+ }
168
+ catch (err) {
169
+ results.providers.github = { success: false, error: err.message };
170
+ }
171
+ }
172
+ // Gitea
173
+ if (ctx.providerManager.giteaBaseUrl && giteaOwner) {
174
+ try {
175
+ const commits = await axios_1.default.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repoInfo.repoName}/commits`, {
176
+ params: {
177
+ sha: branch,
178
+ since: params.since,
179
+ limit: params.limit || 50,
180
+ },
181
+ headers: { Authorization: `token ${ctx.providerManager.giteaToken}` }
182
+ });
183
+ results.providers.gitea = {
184
+ success: true,
185
+ commits: commits.data.map((c) => ({
186
+ sha: c.sha,
187
+ message: c.commit.message,
188
+ author: c.commit.author.name,
189
+ email: c.commit.author.email,
190
+ date: c.commit.author.date,
191
+ })),
192
+ };
193
+ }
194
+ catch (err) {
195
+ results.providers.gitea = { success: false, error: err.message };
196
+ }
197
+ }
198
+ return results;
77
199
  }
78
200
  case 'contributors': {
79
201
  const log = await git.log();
@@ -92,7 +214,55 @@ class GitMonitorTool {
92
214
  }
93
215
  });
94
216
  const sorted = Array.from(contributors.values()).sort((a, b) => b.commits - a.commits);
95
- return { success: true, contributors: sorted };
217
+ const results = {
218
+ success: true,
219
+ local: { contributors: sorted },
220
+ providers: {}
221
+ };
222
+ // Also query remote APIs
223
+ const repoInfo = (0, repoHelpers_1.getRepoInfo)(projectPath);
224
+ const githubOwner = process.env.GITHUB_USERNAME;
225
+ const giteaOwner = process.env.GITEA_USERNAME;
226
+ // GitHub
227
+ if (ctx.providerManager.github && githubOwner) {
228
+ try {
229
+ const contributors = await ctx.providerManager.github.rest.repos.listContributors({
230
+ owner: githubOwner,
231
+ repo: repoInfo.repoName,
232
+ });
233
+ results.providers.github = {
234
+ success: true,
235
+ contributors: contributors.data.map((c) => ({
236
+ login: c.login,
237
+ contributions: c.contributions,
238
+ avatar_url: c.avatar_url,
239
+ url: c.html_url,
240
+ })),
241
+ };
242
+ }
243
+ catch (err) {
244
+ results.providers.github = { success: false, error: err.message };
245
+ }
246
+ }
247
+ // Gitea
248
+ if (ctx.providerManager.giteaBaseUrl && giteaOwner) {
249
+ try {
250
+ const contributors = await axios_1.default.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repoInfo.repoName}/contributors`, { headers: { Authorization: `token ${ctx.providerManager.giteaToken}` } });
251
+ results.providers.gitea = {
252
+ success: true,
253
+ contributors: contributors.data.map((c) => ({
254
+ login: c.login,
255
+ name: c.name || c.login,
256
+ contributions: c.contributions,
257
+ avatar_url: c.avatar_url,
258
+ })),
259
+ };
260
+ }
261
+ catch (err) {
262
+ results.providers.gitea = { success: false, error: err.message };
263
+ }
264
+ }
265
+ return results;
96
266
  }
97
267
  default:
98
268
  throw new errors_1.MCPError('VALIDATION_ERROR', `Unsupported action: ${action}`);
@@ -2,29 +2,5 @@ import { Tool, MCPContext } from '../types';
2
2
  export declare class GitTagsTool implements Tool {
3
3
  name: string;
4
4
  description: string;
5
- handle(params: Record<string, any>, ctx: MCPContext): Promise<{
6
- success: boolean;
7
- tag: any;
8
- tags?: undefined;
9
- details?: undefined;
10
- deleted?: undefined;
11
- } | {
12
- success: boolean;
13
- tags: string[];
14
- tag?: undefined;
15
- details?: undefined;
16
- deleted?: undefined;
17
- } | {
18
- success: boolean;
19
- tag: any;
20
- details: string;
21
- tags?: undefined;
22
- deleted?: undefined;
23
- } | {
24
- success: boolean;
25
- deleted: any;
26
- tag?: undefined;
27
- tags?: undefined;
28
- details?: undefined;
29
- }>;
5
+ handle(params: Record<string, any>, ctx: MCPContext): Promise<any>;
30
6
  }
@@ -6,10 +6,12 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.GitTagsTool = void 0;
7
7
  const simple_git_1 = __importDefault(require("simple-git"));
8
8
  const errors_1 = require("../utils/errors");
9
+ const axios_1 = __importDefault(require("axios"));
10
+ const repoHelpers_1 = require("../utils/repoHelpers");
9
11
  class GitTagsTool {
10
12
  constructor() {
11
13
  this.name = 'git-tags';
12
- this.description = 'Tag management operations';
14
+ this.description = 'Tag management operations - automatic dual-provider execution for remote queries';
13
15
  }
14
16
  async handle(params, ctx) {
15
17
  const action = params.action;
@@ -29,37 +31,183 @@ class GitTagsTool {
29
31
  else {
30
32
  await git.addTag(tagName);
31
33
  }
32
- return { success: true, tag: tagName };
34
+ return { success: true, tag: tagName, local: true };
33
35
  }
34
36
  case 'list': {
35
- const tags = await git.tags();
36
- return { success: true, tags: tags.all };
37
+ const localTags = await git.tags();
38
+ const results = {
39
+ success: true,
40
+ local: { tags: localTags.all },
41
+ providers: {}
42
+ };
43
+ // Also query remote APIs
44
+ const repoInfo = (0, repoHelpers_1.getRepoInfo)(projectPath);
45
+ const githubOwner = process.env.GITHUB_USERNAME;
46
+ const giteaOwner = process.env.GITEA_USERNAME;
47
+ // GitHub
48
+ if (ctx.providerManager.github && githubOwner) {
49
+ try {
50
+ const tags = await ctx.providerManager.github.rest.repos.listTags({
51
+ owner: githubOwner,
52
+ repo: repoInfo.repoName,
53
+ });
54
+ results.providers.github = {
55
+ success: true,
56
+ tags: tags.data.map((t) => ({
57
+ name: t.name,
58
+ sha: t.commit.sha,
59
+ zipball_url: t.zipball_url,
60
+ tarball_url: t.tarball_url,
61
+ })),
62
+ };
63
+ }
64
+ catch (err) {
65
+ results.providers.github = { success: false, error: err.message };
66
+ }
67
+ }
68
+ // Gitea
69
+ if (ctx.providerManager.giteaBaseUrl && giteaOwner) {
70
+ try {
71
+ const tags = await axios_1.default.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repoInfo.repoName}/tags`, { headers: { Authorization: `token ${ctx.providerManager.giteaToken}` } });
72
+ results.providers.gitea = {
73
+ success: true,
74
+ tags: tags.data.map((t) => ({
75
+ name: t.name,
76
+ sha: t.commit?.id || t.id,
77
+ zipball_url: t.zipball_url,
78
+ tarball_url: t.tarball_url,
79
+ })),
80
+ };
81
+ }
82
+ catch (err) {
83
+ results.providers.gitea = { success: false, error: err.message };
84
+ }
85
+ }
86
+ return results;
37
87
  }
38
88
  case 'get': {
39
89
  const tagName = params.tagName;
40
90
  if (!tagName)
41
91
  throw new errors_1.MCPError('VALIDATION_ERROR', 'tagName is required');
42
- const tags = await git.tags();
43
- if (!tags.all.includes(tagName)) {
44
- throw new errors_1.MCPError('TAG_NOT_FOUND', `Tag '${tagName}' not found`);
92
+ const localTags = await git.tags();
93
+ const localTag = localTags.all.find(t => t === tagName);
94
+ const results = {
95
+ success: true,
96
+ local: localTag ? { found: true, tag: tagName } : { found: false },
97
+ providers: {}
98
+ };
99
+ if (localTag) {
100
+ try {
101
+ const show = await git.show([tagName]);
102
+ results.local.details = show;
103
+ }
104
+ catch { }
105
+ }
106
+ // Also query remote APIs
107
+ const repoInfo = (0, repoHelpers_1.getRepoInfo)(projectPath);
108
+ const githubOwner = process.env.GITHUB_USERNAME;
109
+ const giteaOwner = process.env.GITEA_USERNAME;
110
+ // GitHub
111
+ if (ctx.providerManager.github && githubOwner) {
112
+ try {
113
+ const tag = await ctx.providerManager.github.rest.git.getRef({
114
+ owner: githubOwner,
115
+ repo: repoInfo.repoName,
116
+ ref: `tags/${tagName}`,
117
+ });
118
+ results.providers.github = {
119
+ success: true,
120
+ tag: {
121
+ name: tagName,
122
+ sha: tag.data.object.sha,
123
+ type: tag.data.object.type,
124
+ url: tag.data.url,
125
+ },
126
+ };
127
+ }
128
+ catch (err) {
129
+ results.providers.github = { success: false, error: err.message };
130
+ }
45
131
  }
46
- const show = await git.show([tagName]);
47
- return { success: true, tag: tagName, details: show };
132
+ // Gitea
133
+ if (ctx.providerManager.giteaBaseUrl && giteaOwner) {
134
+ try {
135
+ const tags = await axios_1.default.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repoInfo.repoName}/tags`, { headers: { Authorization: `token ${ctx.providerManager.giteaToken}` } });
136
+ const tag = tags.data.find((t) => t.name === tagName);
137
+ if (tag) {
138
+ results.providers.gitea = {
139
+ success: true,
140
+ tag: {
141
+ name: tag.name,
142
+ sha: tag.commit?.id || tag.id,
143
+ message: tag.message,
144
+ },
145
+ };
146
+ }
147
+ else {
148
+ results.providers.gitea = { success: false, error: 'Tag not found' };
149
+ }
150
+ }
151
+ catch (err) {
152
+ results.providers.gitea = { success: false, error: err.message };
153
+ }
154
+ }
155
+ return results;
48
156
  }
49
157
  case 'delete': {
50
158
  const tagName = params.tagName;
51
159
  if (!tagName)
52
160
  throw new errors_1.MCPError('VALIDATION_ERROR', 'tagName is required');
53
161
  await git.tag(['-d', tagName]);
54
- return { success: true, deleted: tagName };
162
+ return { success: true, deleted: tagName, local: true };
55
163
  }
56
164
  case 'search': {
57
165
  const pattern = params.pattern;
58
166
  if (!pattern)
59
167
  throw new errors_1.MCPError('VALIDATION_ERROR', 'pattern is required');
60
- const tags = await git.tags();
61
- const filtered = tags.all.filter(t => t.includes(pattern));
62
- return { success: true, tags: filtered };
168
+ const localTags = await git.tags();
169
+ const filtered = localTags.all.filter(t => t.includes(pattern));
170
+ const results = {
171
+ success: true,
172
+ local: { tags: filtered },
173
+ providers: {}
174
+ };
175
+ // Also search remote APIs
176
+ const repoInfo = (0, repoHelpers_1.getRepoInfo)(projectPath);
177
+ const githubOwner = process.env.GITHUB_USERNAME;
178
+ const giteaOwner = process.env.GITEA_USERNAME;
179
+ // GitHub
180
+ if (ctx.providerManager.github && githubOwner) {
181
+ try {
182
+ const tags = await ctx.providerManager.github.rest.repos.listTags({
183
+ owner: githubOwner,
184
+ repo: repoInfo.repoName,
185
+ });
186
+ const matchedTags = tags.data.filter((t) => t.name.includes(pattern));
187
+ results.providers.github = {
188
+ success: true,
189
+ tags: matchedTags.map((t) => t.name),
190
+ };
191
+ }
192
+ catch (err) {
193
+ results.providers.github = { success: false, error: err.message };
194
+ }
195
+ }
196
+ // Gitea
197
+ if (ctx.providerManager.giteaBaseUrl && giteaOwner) {
198
+ try {
199
+ const tags = await axios_1.default.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repoInfo.repoName}/tags`, { headers: { Authorization: `token ${ctx.providerManager.giteaToken}` } });
200
+ const matchedTags = tags.data.filter((t) => t.name.includes(pattern));
201
+ results.providers.gitea = {
202
+ success: true,
203
+ tags: matchedTags.map((t) => t.name),
204
+ };
205
+ }
206
+ catch (err) {
207
+ results.providers.gitea = { success: false, error: err.message };
208
+ }
209
+ }
210
+ return results;
63
211
  }
64
212
  default:
65
213
  throw new errors_1.MCPError('VALIDATION_ERROR', `Unsupported action: ${action}`);
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Dual Provider Execution Helper
3
+ * Executes operations on BOTH GitHub and Gitea simultaneously
4
+ * Uses GITHUB_USERNAME and GITEA_USERNAME from env vars automatically
5
+ */
6
+ import { ProviderManager } from '../providers/providerManager';
7
+ export interface DualExecutionResult<T> {
8
+ github: {
9
+ success: boolean;
10
+ data?: T;
11
+ error?: string;
12
+ };
13
+ gitea: {
14
+ success: boolean;
15
+ data?: T;
16
+ error?: string;
17
+ };
18
+ }
19
+ /**
20
+ * Execute function on both providers simultaneously
21
+ * @param providerManager Provider manager instance
22
+ * @param githubFn Function to execute on GitHub
23
+ * @param giteaFn Function to execute on Gitea
24
+ * @returns Dual execution result with separate responses
25
+ */
26
+ export declare function executeDual<T>(providerManager: ProviderManager, githubFn: () => Promise<T>, giteaFn: () => Promise<T>): Promise<DualExecutionResult<T>>;
27
+ /**
28
+ * Get GitHub username from env vars
29
+ * @throws Error if GITHUB_USERNAME not found
30
+ */
31
+ export declare function getGitHubUsername(): string;
32
+ /**
33
+ * Get Gitea username from env vars
34
+ * @throws Error if GITEA_USERNAME not found
35
+ */
36
+ export declare function getGiteaUsername(): string;
37
+ /**
38
+ * Get repository name from projectPath
39
+ * @param projectPath Absolute path to project root
40
+ * @returns Repository name (folder name)
41
+ */
42
+ export declare function getRepoNameFromPath(projectPath: string): string;
43
+ /**
44
+ * Execute same function on both providers with automatic username injection
45
+ * @param providerManager Provider manager instance
46
+ * @param fn Function that takes (provider, username) and returns data
47
+ * @returns Dual execution result
48
+ */
49
+ export declare function executeDualAuto<T>(providerManager: ProviderManager, fn: (provider: any, username: string) => Promise<T>): Promise<DualExecutionResult<T>>;
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ /**
3
+ * Dual Provider Execution Helper
4
+ * Executes operations on BOTH GitHub and Gitea simultaneously
5
+ * Uses GITHUB_USERNAME and GITEA_USERNAME from env vars automatically
6
+ */
7
+ Object.defineProperty(exports, "__esModule", { value: true });
8
+ exports.executeDual = executeDual;
9
+ exports.getGitHubUsername = getGitHubUsername;
10
+ exports.getGiteaUsername = getGiteaUsername;
11
+ exports.getRepoNameFromPath = getRepoNameFromPath;
12
+ exports.executeDualAuto = executeDualAuto;
13
+ /**
14
+ * Execute function on both providers simultaneously
15
+ * @param providerManager Provider manager instance
16
+ * @param githubFn Function to execute on GitHub
17
+ * @param giteaFn Function to execute on Gitea
18
+ * @returns Dual execution result with separate responses
19
+ */
20
+ async function executeDual(providerManager, githubFn, giteaFn) {
21
+ const result = {
22
+ github: { success: false },
23
+ gitea: { success: false }
24
+ };
25
+ // Execute GitHub
26
+ try {
27
+ const githubData = await githubFn();
28
+ result.github = {
29
+ success: true,
30
+ data: githubData
31
+ };
32
+ }
33
+ catch (error) {
34
+ result.github = {
35
+ success: false,
36
+ error: error.message || String(error)
37
+ };
38
+ }
39
+ // Execute Gitea
40
+ try {
41
+ const giteaData = await giteaFn();
42
+ result.gitea = {
43
+ success: true,
44
+ data: giteaData
45
+ };
46
+ }
47
+ catch (error) {
48
+ result.gitea = {
49
+ success: false,
50
+ error: error.message || String(error)
51
+ };
52
+ }
53
+ return result;
54
+ }
55
+ /**
56
+ * Get GitHub username from env vars
57
+ * @throws Error if GITHUB_USERNAME not found
58
+ */
59
+ function getGitHubUsername() {
60
+ const username = process.env.GITHUB_USERNAME;
61
+ if (!username) {
62
+ throw new Error('GITHUB_USERNAME not found in environment variables');
63
+ }
64
+ return username;
65
+ }
66
+ /**
67
+ * Get Gitea username from env vars
68
+ * @throws Error if GITEA_USERNAME not found
69
+ */
70
+ function getGiteaUsername() {
71
+ const username = process.env.GITEA_USERNAME;
72
+ if (!username) {
73
+ throw new Error('GITEA_USERNAME not found in environment variables');
74
+ }
75
+ return username;
76
+ }
77
+ /**
78
+ * Get repository name from projectPath
79
+ * @param projectPath Absolute path to project root
80
+ * @returns Repository name (folder name)
81
+ */
82
+ function getRepoNameFromPath(projectPath) {
83
+ const parts = projectPath.replace(/\\/g, '/').split('/');
84
+ return parts[parts.length - 1];
85
+ }
86
+ /**
87
+ * Execute same function on both providers with automatic username injection
88
+ * @param providerManager Provider manager instance
89
+ * @param fn Function that takes (provider, username) and returns data
90
+ * @returns Dual execution result
91
+ */
92
+ async function executeDualAuto(providerManager, fn) {
93
+ const githubUsername = getGitHubUsername();
94
+ const giteaUsername = getGiteaUsername();
95
+ return executeDual(providerManager, () => fn(providerManager.getProvider('github'), githubUsername), () => fn(providerManager.getProvider('gitea'), giteaUsername));
96
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@andrebuzeli/git-mcp",
3
- "version": "6.3.2",
4
- "description": "Professional MCP server for Git operations - STDIO UNIVERSAL: works in ANY IDE (Cursor, VSCode, Claude Desktop). Fully autonomous with intelligent error handling. Auto-detects branches, owner, repo. User-friendly error messages. Dual-provider execution (GitHub + Gitea)",
3
+ "version": "7.0.0",
4
+ "description": "Professional MCP server for Git operations - STDIO UNIVERSAL: works in ANY IDE (Cursor, VSCode, Claude Desktop). Fully autonomous DUAL execution (GitHub + Gitea APIs) with automatic username detection. All tools execute on BOTH providers simultaneously. No manual parameters needed.",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
7
7
  "bin": {