@andrebuzeli/git-mcp 5.8.4 → 6.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.
- package/dist/index.js +47 -174
- package/dist/tools/gitIssues.js +51 -32
- package/dist/tools/gitRemote.js +39 -8
- package/dist/tools/gitUpload.js +4 -3
- package/dist/utils/repoHelpers.d.ts +38 -0
- package/dist/utils/repoHelpers.js +81 -0
- package/package.json +84 -84
package/dist/index.js
CHANGED
|
@@ -1,43 +1,10 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
4
|
-
if (k2 === undefined) k2 = k;
|
|
5
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
6
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
7
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
8
|
-
}
|
|
9
|
-
Object.defineProperty(o, k2, desc);
|
|
10
|
-
}) : (function(o, m, k, k2) {
|
|
11
|
-
if (k2 === undefined) k2 = k;
|
|
12
|
-
o[k2] = m[k];
|
|
13
|
-
}));
|
|
14
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
15
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
16
|
-
}) : function(o, v) {
|
|
17
|
-
o["default"] = v;
|
|
18
|
-
});
|
|
19
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
20
|
-
var ownKeys = function(o) {
|
|
21
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
22
|
-
var ar = [];
|
|
23
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
24
|
-
return ar;
|
|
25
|
-
};
|
|
26
|
-
return ownKeys(o);
|
|
27
|
-
};
|
|
28
|
-
return function (mod) {
|
|
29
|
-
if (mod && mod.__esModule) return mod;
|
|
30
|
-
var result = {};
|
|
31
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
32
|
-
__setModuleDefault(result, mod);
|
|
33
|
-
return result;
|
|
34
|
-
};
|
|
35
|
-
})();
|
|
36
3
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
37
4
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
38
5
|
};
|
|
39
6
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
40
|
-
const
|
|
7
|
+
const server_1 = require("./server");
|
|
41
8
|
const providerManager_1 = require("./providers/providerManager");
|
|
42
9
|
const config_1 = require("./config");
|
|
43
10
|
const gitFiles_1 = require("./tools/gitFiles");
|
|
@@ -98,157 +65,63 @@ async function main() {
|
|
|
98
65
|
const resources = [
|
|
99
66
|
toolsGuide_1.default
|
|
100
67
|
];
|
|
101
|
-
const toolRegistry = new Map();
|
|
102
|
-
for (const t of tools)
|
|
103
|
-
toolRegistry.set(t.name, t);
|
|
104
|
-
const resourceRegistry = new Map();
|
|
105
|
-
for (const r of resources)
|
|
106
|
-
resourceRegistry.set(r.uri, r);
|
|
107
68
|
// Silent mode for MCP clients - only log to stderr in debug mode
|
|
108
69
|
if (process.env.DEBUG) {
|
|
109
70
|
console.error(`Registered ${tools.length} Git tools`);
|
|
110
71
|
console.error(`Registered ${resources.length} resource(s)`);
|
|
111
72
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
let request;
|
|
120
|
-
let id = undefined;
|
|
121
|
-
try {
|
|
122
|
-
request = JSON.parse(line);
|
|
123
|
-
id = request.id;
|
|
124
|
-
const { jsonrpc, method, params } = request;
|
|
125
|
-
// Only respond if there's an id (not a notification)
|
|
126
|
-
if (id === undefined) {
|
|
73
|
+
const app = (0, server_1.createServer)({ tools, providerManager, resources });
|
|
74
|
+
// Try default port, then find available port if occupied
|
|
75
|
+
let port = parseInt(process.env.PORT || '3210');
|
|
76
|
+
const startServer = (attemptPort) => {
|
|
77
|
+
return new Promise((resolve, reject) => {
|
|
78
|
+
const server = app.listen(attemptPort, () => {
|
|
79
|
+
// Send success message to stderr (MCP clients expect JSON on stdout)
|
|
127
80
|
if (process.env.DEBUG) {
|
|
128
|
-
console.error(
|
|
81
|
+
console.error(`✅ git-mcp server ready on http://localhost:${attemptPort}`);
|
|
82
|
+
console.error(`Tools: ${tools.map(t => t.name).join(', ')}`);
|
|
83
|
+
console.error(`Resources: ${resources.map(r => r.uri).join(', ')}`);
|
|
129
84
|
}
|
|
130
|
-
|
|
131
|
-
}
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
type: 'object',
|
|
154
|
-
properties: {},
|
|
155
|
-
required: []
|
|
156
|
-
}
|
|
157
|
-
}))
|
|
158
|
-
};
|
|
159
|
-
break;
|
|
160
|
-
case 'tools/call':
|
|
161
|
-
const toolName = params?.name;
|
|
162
|
-
const tool = toolRegistry.get(toolName);
|
|
163
|
-
if (!tool) {
|
|
164
|
-
response.error = {
|
|
165
|
-
code: -32601,
|
|
166
|
-
message: `Tool not found: ${toolName}`
|
|
167
|
-
};
|
|
168
|
-
}
|
|
169
|
-
else {
|
|
170
|
-
try {
|
|
171
|
-
const result = await tool.handle(params?.arguments ?? {}, { providerManager });
|
|
172
|
-
response.result = {
|
|
173
|
-
content: [
|
|
174
|
-
{
|
|
175
|
-
type: 'text',
|
|
176
|
-
text: JSON.stringify(result, null, 2)
|
|
177
|
-
}
|
|
178
|
-
]
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
catch (err) {
|
|
182
|
-
response.error = {
|
|
183
|
-
code: -32603,
|
|
184
|
-
message: err.message || String(err)
|
|
185
|
-
};
|
|
186
|
-
}
|
|
187
|
-
}
|
|
188
|
-
break;
|
|
189
|
-
case 'resources/list':
|
|
190
|
-
response.result = {
|
|
191
|
-
resources: Array.from(resourceRegistry.values()).map(r => ({
|
|
192
|
-
uri: r.uri,
|
|
193
|
-
name: r.name,
|
|
194
|
-
description: r.description,
|
|
195
|
-
mimeType: r.mimeType
|
|
196
|
-
}))
|
|
197
|
-
};
|
|
198
|
-
break;
|
|
199
|
-
case 'resources/read':
|
|
200
|
-
const uri = params?.uri;
|
|
201
|
-
const resource = resourceRegistry.get(uri);
|
|
202
|
-
if (!resource) {
|
|
203
|
-
response.error = {
|
|
204
|
-
code: -32601,
|
|
205
|
-
message: `Resource not found: ${uri}`
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
else {
|
|
209
|
-
response.result = {
|
|
210
|
-
contents: [
|
|
211
|
-
{
|
|
212
|
-
uri: resource.uri,
|
|
213
|
-
mimeType: resource.mimeType,
|
|
214
|
-
text: resource.content
|
|
215
|
-
}
|
|
216
|
-
]
|
|
217
|
-
};
|
|
218
|
-
}
|
|
219
|
-
break;
|
|
220
|
-
default:
|
|
221
|
-
response.error = {
|
|
222
|
-
code: -32601,
|
|
223
|
-
message: `Method not found: ${method}`
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
console.log(JSON.stringify(response));
|
|
227
|
-
}
|
|
228
|
-
catch (err) {
|
|
229
|
-
// Only send error response if we have a valid id
|
|
230
|
-
if (id !== undefined) {
|
|
231
|
-
const errorResponse = {
|
|
232
|
-
jsonrpc: '2.0',
|
|
233
|
-
id: id,
|
|
234
|
-
error: {
|
|
235
|
-
code: -32700,
|
|
236
|
-
message: `Parse error: ${err.message || String(err)}`
|
|
237
|
-
}
|
|
238
|
-
};
|
|
239
|
-
console.log(JSON.stringify(errorResponse));
|
|
240
|
-
}
|
|
241
|
-
if (process.env.DEBUG) {
|
|
242
|
-
console.error('Error processing request:', err);
|
|
243
|
-
}
|
|
85
|
+
resolve(server);
|
|
86
|
+
});
|
|
87
|
+
server.on('error', (err) => {
|
|
88
|
+
if (err.code === 'EADDRINUSE') {
|
|
89
|
+
// Port in use, try next port
|
|
90
|
+
server.close();
|
|
91
|
+
resolve(null);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
reject(err);
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
};
|
|
99
|
+
// Try to start server on available port
|
|
100
|
+
let server = null;
|
|
101
|
+
const maxAttempts = 10;
|
|
102
|
+
for (let attempt = 0; attempt < maxAttempts; attempt++) {
|
|
103
|
+
const tryPort = port + attempt;
|
|
104
|
+
server = await startServer(tryPort);
|
|
105
|
+
if (server) {
|
|
106
|
+
port = tryPort;
|
|
107
|
+
break;
|
|
244
108
|
}
|
|
109
|
+
}
|
|
110
|
+
if (!server) {
|
|
111
|
+
console.error(`❌ Failed to find available port after ${maxAttempts} attempts`);
|
|
112
|
+
process.exit(1);
|
|
113
|
+
}
|
|
114
|
+
server.on('error', (err) => {
|
|
115
|
+
console.error('❌ Server error:', err);
|
|
116
|
+
process.exit(1);
|
|
245
117
|
});
|
|
246
118
|
// Keep process alive
|
|
247
119
|
process.on('SIGINT', () => {
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
120
|
+
console.log('\n👋 Shutting down server...');
|
|
121
|
+
server.close(() => {
|
|
122
|
+
console.log('Server closed');
|
|
123
|
+
process.exit(0);
|
|
124
|
+
});
|
|
252
125
|
});
|
|
253
126
|
}
|
|
254
127
|
main().catch(err => {
|
package/dist/tools/gitIssues.js
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.GitIssuesTool = void 0;
|
|
4
4
|
const errors_1 = require("../utils/errors");
|
|
5
|
+
const repoHelpers_1 = require("../utils/repoHelpers");
|
|
5
6
|
class GitIssuesTool {
|
|
6
7
|
constructor() {
|
|
7
8
|
this.name = 'git-issues';
|
|
@@ -11,18 +12,27 @@ class GitIssuesTool {
|
|
|
11
12
|
const action = params.action;
|
|
12
13
|
if (!action)
|
|
13
14
|
throw new errors_1.MCPError('VALIDATION_ERROR', 'action is required');
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
// Auto-extract repo info if projectPath is provided
|
|
16
|
+
let repo = params.repo;
|
|
17
|
+
if (!repo && params.projectPath) {
|
|
18
|
+
const repoInfo = (0, repoHelpers_1.getRepoInfo)(params.projectPath);
|
|
19
|
+
repo = repoInfo.repoName;
|
|
20
|
+
}
|
|
21
|
+
// Each provider uses its own username from env
|
|
22
|
+
const githubOwner = process.env.GITHUB_USERNAME;
|
|
23
|
+
const giteaOwner = process.env.GITEA_USERNAME;
|
|
16
24
|
switch (action) {
|
|
17
25
|
case 'create': {
|
|
18
26
|
if (!params.title)
|
|
19
27
|
throw new errors_1.MCPError('VALIDATION_ERROR', 'title is required');
|
|
28
|
+
if (!repo)
|
|
29
|
+
throw new errors_1.MCPError('VALIDATION_ERROR', 'repo is required (or provide projectPath)');
|
|
20
30
|
const results = { success: true, providers: {} };
|
|
21
31
|
// GitHub
|
|
22
|
-
if (ctx.providerManager.github) {
|
|
32
|
+
if (ctx.providerManager.github && githubOwner) {
|
|
23
33
|
try {
|
|
24
34
|
const result = await ctx.providerManager.github.rest.issues.create({
|
|
25
|
-
owner:
|
|
35
|
+
owner: githubOwner,
|
|
26
36
|
repo: repo,
|
|
27
37
|
title: params.title,
|
|
28
38
|
body: params.body,
|
|
@@ -37,10 +47,10 @@ class GitIssuesTool {
|
|
|
37
47
|
}
|
|
38
48
|
}
|
|
39
49
|
// Gitea
|
|
40
|
-
if (ctx.providerManager.giteaBaseUrl) {
|
|
50
|
+
if (ctx.providerManager.giteaBaseUrl && giteaOwner) {
|
|
41
51
|
try {
|
|
42
52
|
const axios = require('axios');
|
|
43
|
-
const result = await axios.post(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${
|
|
53
|
+
const result = await axios.post(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repo}/issues`, {
|
|
44
54
|
title: params.title,
|
|
45
55
|
body: params.body,
|
|
46
56
|
labels: params.labels?.map((l) => ({ name: l })),
|
|
@@ -57,12 +67,14 @@ class GitIssuesTool {
|
|
|
57
67
|
return results;
|
|
58
68
|
}
|
|
59
69
|
case 'list': {
|
|
70
|
+
if (!repo)
|
|
71
|
+
throw new errors_1.MCPError('VALIDATION_ERROR', 'repo is required (or provide projectPath)');
|
|
60
72
|
const results = { success: true, providers: {} };
|
|
61
73
|
// GitHub
|
|
62
|
-
if (ctx.providerManager.github) {
|
|
74
|
+
if (ctx.providerManager.github && githubOwner) {
|
|
63
75
|
try {
|
|
64
76
|
const result = await ctx.providerManager.github.rest.issues.listForRepo({
|
|
65
|
-
owner:
|
|
77
|
+
owner: githubOwner,
|
|
66
78
|
repo: repo,
|
|
67
79
|
state: params.state_filter || 'open',
|
|
68
80
|
sort: params.sort || 'created',
|
|
@@ -76,10 +88,10 @@ class GitIssuesTool {
|
|
|
76
88
|
}
|
|
77
89
|
}
|
|
78
90
|
// Gitea
|
|
79
|
-
if (ctx.providerManager.giteaBaseUrl) {
|
|
91
|
+
if (ctx.providerManager.giteaBaseUrl && giteaOwner) {
|
|
80
92
|
try {
|
|
81
93
|
const axios = require('axios');
|
|
82
|
-
const result = await axios.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${
|
|
94
|
+
const result = await axios.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repo}/issues`, {
|
|
83
95
|
params: {
|
|
84
96
|
state: params.state_filter || 'open',
|
|
85
97
|
sort: params.sort || 'created',
|
|
@@ -99,12 +111,14 @@ class GitIssuesTool {
|
|
|
99
111
|
const issue_number = params.issue_number;
|
|
100
112
|
if (!issue_number)
|
|
101
113
|
throw new errors_1.MCPError('VALIDATION_ERROR', 'issue_number is required');
|
|
114
|
+
if (!repo)
|
|
115
|
+
throw new errors_1.MCPError('VALIDATION_ERROR', 'repo is required (or provide projectPath)');
|
|
102
116
|
const results = { success: true, providers: {} };
|
|
103
117
|
// GitHub
|
|
104
|
-
if (ctx.providerManager.github) {
|
|
118
|
+
if (ctx.providerManager.github && githubOwner) {
|
|
105
119
|
try {
|
|
106
120
|
const result = await ctx.providerManager.github.rest.issues.get({
|
|
107
|
-
owner:
|
|
121
|
+
owner: githubOwner,
|
|
108
122
|
repo: repo,
|
|
109
123
|
issue_number,
|
|
110
124
|
});
|
|
@@ -115,10 +129,10 @@ class GitIssuesTool {
|
|
|
115
129
|
}
|
|
116
130
|
}
|
|
117
131
|
// Gitea
|
|
118
|
-
if (ctx.providerManager.giteaBaseUrl) {
|
|
132
|
+
if (ctx.providerManager.giteaBaseUrl && giteaOwner) {
|
|
119
133
|
try {
|
|
120
134
|
const axios = require('axios');
|
|
121
|
-
const result = await axios.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${
|
|
135
|
+
const result = await axios.get(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repo}/issues/${issue_number}`, {
|
|
122
136
|
headers: { Authorization: `token ${ctx.providerManager.giteaToken}` }
|
|
123
137
|
});
|
|
124
138
|
results.providers.gitea = { success: true, issue: result.data };
|
|
@@ -133,20 +147,19 @@ class GitIssuesTool {
|
|
|
133
147
|
const issue_number = params.issue_number;
|
|
134
148
|
if (!issue_number)
|
|
135
149
|
throw new errors_1.MCPError('VALIDATION_ERROR', 'issue_number is required');
|
|
150
|
+
if (!repo)
|
|
151
|
+
throw new errors_1.MCPError('VALIDATION_ERROR', 'repo is required (or provide projectPath)');
|
|
136
152
|
const results = { success: true, providers: {} };
|
|
137
153
|
// GitHub
|
|
138
|
-
if (ctx.providerManager.github) {
|
|
154
|
+
if (ctx.providerManager.github && githubOwner) {
|
|
139
155
|
try {
|
|
140
156
|
const result = await ctx.providerManager.github.rest.issues.update({
|
|
141
|
-
owner:
|
|
157
|
+
owner: githubOwner,
|
|
142
158
|
repo: repo,
|
|
143
|
-
issue_number,
|
|
159
|
+
issue_number: issue_number,
|
|
144
160
|
title: params.title,
|
|
145
161
|
body: params.body,
|
|
146
162
|
state: params.state,
|
|
147
|
-
labels: params.labels,
|
|
148
|
-
assignees: params.assignees,
|
|
149
|
-
milestone: params.milestone,
|
|
150
163
|
});
|
|
151
164
|
results.providers.github = { success: true, issue: result.data };
|
|
152
165
|
}
|
|
@@ -155,10 +168,10 @@ class GitIssuesTool {
|
|
|
155
168
|
}
|
|
156
169
|
}
|
|
157
170
|
// Gitea
|
|
158
|
-
if (ctx.providerManager.giteaBaseUrl) {
|
|
171
|
+
if (ctx.providerManager.giteaBaseUrl && giteaOwner) {
|
|
159
172
|
try {
|
|
160
173
|
const axios = require('axios');
|
|
161
|
-
const result = await axios.patch(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${
|
|
174
|
+
const result = await axios.patch(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repo}/issues/${issue_number}`, {
|
|
162
175
|
title: params.title,
|
|
163
176
|
body: params.body,
|
|
164
177
|
state: params.state,
|
|
@@ -177,12 +190,14 @@ class GitIssuesTool {
|
|
|
177
190
|
const issue_number = params.issue_number;
|
|
178
191
|
if (!issue_number)
|
|
179
192
|
throw new errors_1.MCPError('VALIDATION_ERROR', 'issue_number is required');
|
|
193
|
+
if (!repo)
|
|
194
|
+
throw new errors_1.MCPError('VALIDATION_ERROR', 'repo is required (or provide projectPath)');
|
|
180
195
|
const results = { success: true, providers: {} };
|
|
181
196
|
// GitHub
|
|
182
|
-
if (ctx.providerManager.github) {
|
|
197
|
+
if (ctx.providerManager.github && githubOwner) {
|
|
183
198
|
try {
|
|
184
199
|
const result = await ctx.providerManager.github.rest.issues.update({
|
|
185
|
-
owner:
|
|
200
|
+
owner: githubOwner,
|
|
186
201
|
repo: repo,
|
|
187
202
|
issue_number,
|
|
188
203
|
state: 'closed',
|
|
@@ -194,10 +209,10 @@ class GitIssuesTool {
|
|
|
194
209
|
}
|
|
195
210
|
}
|
|
196
211
|
// Gitea
|
|
197
|
-
if (ctx.providerManager.giteaBaseUrl) {
|
|
212
|
+
if (ctx.providerManager.giteaBaseUrl && giteaOwner) {
|
|
198
213
|
try {
|
|
199
214
|
const axios = require('axios');
|
|
200
|
-
const result = await axios.patch(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${
|
|
215
|
+
const result = await axios.patch(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repo}/issues/${issue_number}`, { state: 'closed' }, {
|
|
201
216
|
headers: { Authorization: `token ${ctx.providerManager.giteaToken}` }
|
|
202
217
|
});
|
|
203
218
|
results.providers.gitea = { success: true, issue: result.data };
|
|
@@ -215,12 +230,14 @@ class GitIssuesTool {
|
|
|
215
230
|
throw new errors_1.MCPError('VALIDATION_ERROR', 'issue_number is required');
|
|
216
231
|
if (!comment_body)
|
|
217
232
|
throw new errors_1.MCPError('VALIDATION_ERROR', 'comment_body is required');
|
|
233
|
+
if (!repo)
|
|
234
|
+
throw new errors_1.MCPError('VALIDATION_ERROR', 'repo is required (or provide projectPath)');
|
|
218
235
|
const results = { success: true, providers: {} };
|
|
219
236
|
// GitHub
|
|
220
|
-
if (ctx.providerManager.github) {
|
|
237
|
+
if (ctx.providerManager.github && githubOwner) {
|
|
221
238
|
try {
|
|
222
239
|
const result = await ctx.providerManager.github.rest.issues.createComment({
|
|
223
|
-
owner:
|
|
240
|
+
owner: githubOwner,
|
|
224
241
|
repo: repo,
|
|
225
242
|
issue_number,
|
|
226
243
|
body: comment_body,
|
|
@@ -232,10 +249,10 @@ class GitIssuesTool {
|
|
|
232
249
|
}
|
|
233
250
|
}
|
|
234
251
|
// Gitea
|
|
235
|
-
if (ctx.providerManager.giteaBaseUrl) {
|
|
252
|
+
if (ctx.providerManager.giteaBaseUrl && giteaOwner) {
|
|
236
253
|
try {
|
|
237
254
|
const axios = require('axios');
|
|
238
|
-
const result = await axios.post(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${
|
|
255
|
+
const result = await axios.post(`${ctx.providerManager.giteaBaseUrl}/api/v1/repos/${giteaOwner}/${repo}/issues/${issue_number}/comments`, { body: comment_body }, {
|
|
239
256
|
headers: { Authorization: `token ${ctx.providerManager.giteaToken}` }
|
|
240
257
|
});
|
|
241
258
|
results.providers.gitea = { success: true, comment: result.data };
|
|
@@ -250,12 +267,14 @@ class GitIssuesTool {
|
|
|
250
267
|
const query = params.query;
|
|
251
268
|
if (!query)
|
|
252
269
|
throw new errors_1.MCPError('VALIDATION_ERROR', 'query is required');
|
|
270
|
+
if (!repo)
|
|
271
|
+
throw new errors_1.MCPError('VALIDATION_ERROR', 'repo is required (or provide projectPath)');
|
|
253
272
|
const results = { success: true, providers: {} };
|
|
254
273
|
// GitHub
|
|
255
|
-
if (ctx.providerManager.github) {
|
|
274
|
+
if (ctx.providerManager.github && githubOwner) {
|
|
256
275
|
try {
|
|
257
276
|
const result = await ctx.providerManager.github.rest.search.issuesAndPullRequests({
|
|
258
|
-
q: `${query} repo:${
|
|
277
|
+
q: `${query} repo:${githubOwner}/${repo} type:issue`,
|
|
259
278
|
sort: params.search_sort || 'created',
|
|
260
279
|
order: params.search_order || 'desc',
|
|
261
280
|
});
|
package/dist/tools/gitRemote.js
CHANGED
|
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
exports.GitRemoteTool = 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");
|
|
9
10
|
class GitRemoteTool {
|
|
10
11
|
constructor() {
|
|
11
12
|
this.name = 'git-remote';
|
|
@@ -20,10 +21,25 @@ class GitRemoteTool {
|
|
|
20
21
|
const git = (0, simple_git_1.default)({ baseDir: projectPath });
|
|
21
22
|
switch (action) {
|
|
22
23
|
case 'add': {
|
|
23
|
-
const name = params.name;
|
|
24
|
-
|
|
25
|
-
if
|
|
26
|
-
|
|
24
|
+
const name = params.name || 'origin';
|
|
25
|
+
let url = params.url;
|
|
26
|
+
// Auto-construct URL if not provided
|
|
27
|
+
if (!url) {
|
|
28
|
+
const { repoName, githubOwner, giteaOwner } = (0, repoHelpers_1.getRepoInfo)(projectPath);
|
|
29
|
+
if (name === 'github' && githubOwner) {
|
|
30
|
+
url = (0, repoHelpers_1.buildGitHubUrl)(githubOwner, repoName);
|
|
31
|
+
}
|
|
32
|
+
else if (name === 'gitea' && giteaOwner) {
|
|
33
|
+
url = (0, repoHelpers_1.buildGiteaUrl)(giteaOwner, repoName);
|
|
34
|
+
}
|
|
35
|
+
else if (name === 'origin' && githubOwner) {
|
|
36
|
+
// Default to GitHub for 'origin'
|
|
37
|
+
url = (0, repoHelpers_1.buildGitHubUrl)(githubOwner, repoName);
|
|
38
|
+
}
|
|
39
|
+
else {
|
|
40
|
+
throw new errors_1.MCPError('VALIDATION_ERROR', 'url is required or set GITHUB_USERNAME/GITEA_USERNAME in environment');
|
|
41
|
+
}
|
|
42
|
+
}
|
|
27
43
|
await git.addRemote(name, url);
|
|
28
44
|
return { success: true, remote: name, url };
|
|
29
45
|
}
|
|
@@ -48,10 +64,25 @@ class GitRemoteTool {
|
|
|
48
64
|
return { success: true, remotes };
|
|
49
65
|
}
|
|
50
66
|
case 'set-url': {
|
|
51
|
-
const name = params.name;
|
|
52
|
-
|
|
53
|
-
if
|
|
54
|
-
|
|
67
|
+
const name = params.name || 'origin';
|
|
68
|
+
let url = params.url;
|
|
69
|
+
// Auto-construct URL if not provided
|
|
70
|
+
if (!url) {
|
|
71
|
+
const { repoName, githubOwner, giteaOwner } = (0, repoHelpers_1.getRepoInfo)(projectPath);
|
|
72
|
+
if (name === 'github' && githubOwner) {
|
|
73
|
+
url = (0, repoHelpers_1.buildGitHubUrl)(githubOwner, repoName);
|
|
74
|
+
}
|
|
75
|
+
else if (name === 'gitea' && giteaOwner) {
|
|
76
|
+
url = (0, repoHelpers_1.buildGiteaUrl)(giteaOwner, repoName);
|
|
77
|
+
}
|
|
78
|
+
else if (name === 'origin' && githubOwner) {
|
|
79
|
+
// Default to GitHub for 'origin'
|
|
80
|
+
url = (0, repoHelpers_1.buildGitHubUrl)(githubOwner, repoName);
|
|
81
|
+
}
|
|
82
|
+
else {
|
|
83
|
+
throw new errors_1.MCPError('VALIDATION_ERROR', 'url is required or set GITHUB_USERNAME/GITEA_USERNAME in environment');
|
|
84
|
+
}
|
|
85
|
+
}
|
|
55
86
|
await git.remote(['set-url', name, url]);
|
|
56
87
|
return { success: true, remote: name, url };
|
|
57
88
|
}
|
package/dist/tools/gitUpload.js
CHANGED
|
@@ -42,6 +42,7 @@ const errors_1 = require("../utils/errors");
|
|
|
42
42
|
const axios_1 = __importDefault(require("axios"));
|
|
43
43
|
const fs = __importStar(require("fs/promises"));
|
|
44
44
|
const path = __importStar(require("path"));
|
|
45
|
+
const repoHelpers_1 = require("../utils/repoHelpers");
|
|
45
46
|
/**
|
|
46
47
|
* Git Upload Tool - Envia projeto completo para GitHub e Gitea automaticamente
|
|
47
48
|
* Modo DUAL automático com rastreabilidade completa
|
|
@@ -56,7 +57,7 @@ class GitUploadTool {
|
|
|
56
57
|
if (!projectPath) {
|
|
57
58
|
throw new errors_1.MCPError('VALIDATION_ERROR', 'projectPath is required');
|
|
58
59
|
}
|
|
59
|
-
const repoName = params.repoName ||
|
|
60
|
+
const repoName = params.repoName || (0, repoHelpers_1.getRepoNameFromPath)(projectPath);
|
|
60
61
|
const description = params.description || `Project uploaded via git-upload at ${new Date().toISOString()}`;
|
|
61
62
|
const isPrivate = params.private !== undefined ? params.private : true;
|
|
62
63
|
const branch = params.branch || 'master';
|
|
@@ -130,8 +131,8 @@ class GitUploadTool {
|
|
|
130
131
|
}
|
|
131
132
|
// 4. Criar repositórios remotos em ambos providers
|
|
132
133
|
// Nota: Cada provider precisa usar seu próprio username (Gitea é case-sensitive)
|
|
133
|
-
const githubOwner =
|
|
134
|
-
const giteaOwner =
|
|
134
|
+
const githubOwner = process.env.GITHUB_USERNAME;
|
|
135
|
+
const giteaOwner = process.env.GITEA_USERNAME;
|
|
135
136
|
// GITHUB
|
|
136
137
|
if (ctx.providerManager.github) {
|
|
137
138
|
results.traceability.uploadSteps.push({
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Helper functions to extract repository information automatically
|
|
3
|
+
*/
|
|
4
|
+
/**
|
|
5
|
+
* Extract repository name from project path
|
|
6
|
+
* Normalizes: spaces to hyphens, lowercase, removes special chars
|
|
7
|
+
*/
|
|
8
|
+
export declare function getRepoNameFromPath(projectPath: string): string;
|
|
9
|
+
/**
|
|
10
|
+
* Get GitHub owner from environment
|
|
11
|
+
*/
|
|
12
|
+
export declare function getGitHubOwner(): string | undefined;
|
|
13
|
+
/**
|
|
14
|
+
* Get Gitea owner from environment
|
|
15
|
+
*/
|
|
16
|
+
export declare function getGiteaOwner(): string | undefined;
|
|
17
|
+
/**
|
|
18
|
+
* Get Gitea base URL from environment
|
|
19
|
+
*/
|
|
20
|
+
export declare function getGiteaUrl(): string | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* Build GitHub repository URL
|
|
23
|
+
*/
|
|
24
|
+
export declare function buildGitHubUrl(owner: string, repo: string): string;
|
|
25
|
+
/**
|
|
26
|
+
* Build Gitea repository URL with credentials
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildGiteaUrl(owner: string, repo: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* Get repository info from project path and environment
|
|
31
|
+
*/
|
|
32
|
+
export declare function getRepoInfo(projectPath: string): {
|
|
33
|
+
repoName: string;
|
|
34
|
+
githubOwner: string | undefined;
|
|
35
|
+
giteaOwner: string | undefined;
|
|
36
|
+
githubUrl: string | undefined;
|
|
37
|
+
giteaUrl: string | undefined;
|
|
38
|
+
};
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.getRepoNameFromPath = getRepoNameFromPath;
|
|
7
|
+
exports.getGitHubOwner = getGitHubOwner;
|
|
8
|
+
exports.getGiteaOwner = getGiteaOwner;
|
|
9
|
+
exports.getGiteaUrl = getGiteaUrl;
|
|
10
|
+
exports.buildGitHubUrl = buildGitHubUrl;
|
|
11
|
+
exports.buildGiteaUrl = buildGiteaUrl;
|
|
12
|
+
exports.getRepoInfo = getRepoInfo;
|
|
13
|
+
const path_1 = __importDefault(require("path"));
|
|
14
|
+
/**
|
|
15
|
+
* Helper functions to extract repository information automatically
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Extract repository name from project path
|
|
19
|
+
* Normalizes: spaces to hyphens, lowercase, removes special chars
|
|
20
|
+
*/
|
|
21
|
+
function getRepoNameFromPath(projectPath) {
|
|
22
|
+
return path_1.default.basename(projectPath)
|
|
23
|
+
.toLowerCase()
|
|
24
|
+
.replace(/\s+/g, '-')
|
|
25
|
+
.replace(/[^a-z0-9-]/g, '');
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Get GitHub owner from environment
|
|
29
|
+
*/
|
|
30
|
+
function getGitHubOwner() {
|
|
31
|
+
return process.env.GITHUB_USERNAME;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Get Gitea owner from environment
|
|
35
|
+
*/
|
|
36
|
+
function getGiteaOwner() {
|
|
37
|
+
return process.env.GITEA_USERNAME;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Get Gitea base URL from environment
|
|
41
|
+
*/
|
|
42
|
+
function getGiteaUrl() {
|
|
43
|
+
return process.env.GITEA_URL;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Build GitHub repository URL
|
|
47
|
+
*/
|
|
48
|
+
function buildGitHubUrl(owner, repo) {
|
|
49
|
+
return `https://github.com/${owner}/${repo}.git`;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Build Gitea repository URL with credentials
|
|
53
|
+
*/
|
|
54
|
+
function buildGiteaUrl(owner, repo) {
|
|
55
|
+
const baseUrl = getGiteaUrl();
|
|
56
|
+
const token = process.env.GITEA_TOKEN;
|
|
57
|
+
if (!baseUrl) {
|
|
58
|
+
throw new Error('GITEA_URL not configured');
|
|
59
|
+
}
|
|
60
|
+
if (!token) {
|
|
61
|
+
throw new Error('GITEA_TOKEN not configured');
|
|
62
|
+
}
|
|
63
|
+
// Remove protocol from baseUrl
|
|
64
|
+
const urlWithoutProtocol = baseUrl.replace(/^https?:\/\//, '');
|
|
65
|
+
return `http://${owner}:${token}@${urlWithoutProtocol}/${owner}/${repo}.git`;
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Get repository info from project path and environment
|
|
69
|
+
*/
|
|
70
|
+
function getRepoInfo(projectPath) {
|
|
71
|
+
const repoName = getRepoNameFromPath(projectPath);
|
|
72
|
+
const githubOwner = getGitHubOwner();
|
|
73
|
+
const giteaOwner = getGiteaOwner();
|
|
74
|
+
return {
|
|
75
|
+
repoName,
|
|
76
|
+
githubOwner,
|
|
77
|
+
giteaOwner,
|
|
78
|
+
githubUrl: githubOwner ? buildGitHubUrl(githubOwner, repoName) : undefined,
|
|
79
|
+
giteaUrl: giteaOwner ? buildGiteaUrl(giteaOwner, repoName) : undefined,
|
|
80
|
+
};
|
|
81
|
+
}
|
package/package.json
CHANGED
|
@@ -1,84 +1,84 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@andrebuzeli/git-mcp",
|
|
3
|
-
"version": "
|
|
4
|
-
"description": "Professional MCP server for Git operations -
|
|
5
|
-
"main": "dist/index.js",
|
|
6
|
-
"types": "dist/index.d.ts",
|
|
7
|
-
"bin": {
|
|
8
|
-
"git-mcp": "dist/index.js"
|
|
9
|
-
},
|
|
10
|
-
"files": [
|
|
11
|
-
"dist/",
|
|
12
|
-
"README.md",
|
|
13
|
-
"LICENSE",
|
|
14
|
-
"package.json"
|
|
15
|
-
],
|
|
16
|
-
"scripts": {
|
|
17
|
-
"build": "tsc",
|
|
18
|
-
"start": "node dist/index.js",
|
|
19
|
-
"dev": "ts-node src/index.ts",
|
|
20
|
-
"test": "jest",
|
|
21
|
-
"test:watch": "jest --watch",
|
|
22
|
-
"semantic-release": "semantic-release",
|
|
23
|
-
"prepublishOnly": "npm run build",
|
|
24
|
-
"clean": "rimraf dist",
|
|
25
|
-
"release": "npm run build && npm version patch && git push origin --follow-tags"
|
|
26
|
-
},
|
|
27
|
-
"keywords": [
|
|
28
|
-
"mcp",
|
|
29
|
-
"git",
|
|
30
|
-
"github",
|
|
31
|
-
"gitea",
|
|
32
|
-
"universal-mode",
|
|
33
|
-
"multi-provider",
|
|
34
|
-
"model-context-protocol",
|
|
35
|
-
"ai-agent",
|
|
36
|
-
"version-control",
|
|
37
|
-
"automation",
|
|
38
|
-
"cli",
|
|
39
|
-
"developer-tools",
|
|
40
|
-
"workflow",
|
|
41
|
-
"repository-management"
|
|
42
|
-
],
|
|
43
|
-
"author": "Andre Buzeli",
|
|
44
|
-
"license": "MIT",
|
|
45
|
-
"repository": {
|
|
46
|
-
"type": "git",
|
|
47
|
-
"url": "https://github.com/andrebuzeli/git-mcp.git"
|
|
48
|
-
},
|
|
49
|
-
"bugs": {
|
|
50
|
-
"url": "https://github.com/andrebuzeli/git-mcp/issues"
|
|
51
|
-
},
|
|
52
|
-
"homepage": "https://github.com/andrebuzeli/git-mcp#readme",
|
|
53
|
-
"dependencies": {
|
|
54
|
-
"@modelcontextprotocol/sdk": "^0.4.0",
|
|
55
|
-
"@octokit/rest": "^20.0.0",
|
|
56
|
-
"axios": "^1.6.0",
|
|
57
|
-
"simple-git": "^3.20.0",
|
|
58
|
-
"express": "^4.18.2",
|
|
59
|
-
"body-parser": "^1.20.2",
|
|
60
|
-
"ajv": "^8.12.0"
|
|
61
|
-
},
|
|
62
|
-
"devDependencies": {
|
|
63
|
-
"semantic-release": "^20.1.0",
|
|
64
|
-
"@semantic-release/npm": "^10.0.0",
|
|
65
|
-
"@semantic-release/github": "^9.0.0",
|
|
66
|
-
"@semantic-release/commit-analyzer": "^10.0.0",
|
|
67
|
-
"@semantic-release/release-notes-generator": "^10.0.0",
|
|
68
|
-
"@types/node": "^20.0.0",
|
|
69
|
-
"@types/express": "^4.17.17",
|
|
70
|
-
"@types/body-parser": "^1.19.2",
|
|
71
|
-
"@types/jest": "^29.0.0",
|
|
72
|
-
"typescript": "^5.0.0",
|
|
73
|
-
"jest": "^29.0.0",
|
|
74
|
-
"ts-node": "^10.0.0",
|
|
75
|
-
"ts-jest": "^29.0.0",
|
|
76
|
-
"rimraf": "^5.0.0"
|
|
77
|
-
},
|
|
78
|
-
"engines": {
|
|
79
|
-
"node": ">=18.0.0"
|
|
80
|
-
},
|
|
81
|
-
"publishConfig": {
|
|
82
|
-
"access": "public"
|
|
83
|
-
}
|
|
84
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@andrebuzeli/git-mcp",
|
|
3
|
+
"version": "6.0.0",
|
|
4
|
+
"description": "Professional MCP server for Git operations - FULLY AUTONOMOUS: each provider uses own username from env, repo name auto-normalized from projectPath (spaces→hyphens, lowercase, safe chars), zero redundant parameters, automatic dual-provider execution (GitHub + Gitea)",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"types": "dist/index.d.ts",
|
|
7
|
+
"bin": {
|
|
8
|
+
"git-mcp": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist/",
|
|
12
|
+
"README.md",
|
|
13
|
+
"LICENSE",
|
|
14
|
+
"package.json"
|
|
15
|
+
],
|
|
16
|
+
"scripts": {
|
|
17
|
+
"build": "tsc",
|
|
18
|
+
"start": "node dist/index.js",
|
|
19
|
+
"dev": "ts-node src/index.ts",
|
|
20
|
+
"test": "jest",
|
|
21
|
+
"test:watch": "jest --watch",
|
|
22
|
+
"semantic-release": "semantic-release",
|
|
23
|
+
"prepublishOnly": "npm run build",
|
|
24
|
+
"clean": "rimraf dist",
|
|
25
|
+
"release": "npm run build && npm version patch && git push origin --follow-tags"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"mcp",
|
|
29
|
+
"git",
|
|
30
|
+
"github",
|
|
31
|
+
"gitea",
|
|
32
|
+
"universal-mode",
|
|
33
|
+
"multi-provider",
|
|
34
|
+
"model-context-protocol",
|
|
35
|
+
"ai-agent",
|
|
36
|
+
"version-control",
|
|
37
|
+
"automation",
|
|
38
|
+
"cli",
|
|
39
|
+
"developer-tools",
|
|
40
|
+
"workflow",
|
|
41
|
+
"repository-management"
|
|
42
|
+
],
|
|
43
|
+
"author": "Andre Buzeli",
|
|
44
|
+
"license": "MIT",
|
|
45
|
+
"repository": {
|
|
46
|
+
"type": "git",
|
|
47
|
+
"url": "https://github.com/andrebuzeli/git-mcp.git"
|
|
48
|
+
},
|
|
49
|
+
"bugs": {
|
|
50
|
+
"url": "https://github.com/andrebuzeli/git-mcp/issues"
|
|
51
|
+
},
|
|
52
|
+
"homepage": "https://github.com/andrebuzeli/git-mcp#readme",
|
|
53
|
+
"dependencies": {
|
|
54
|
+
"@modelcontextprotocol/sdk": "^0.4.0",
|
|
55
|
+
"@octokit/rest": "^20.0.0",
|
|
56
|
+
"axios": "^1.6.0",
|
|
57
|
+
"simple-git": "^3.20.0",
|
|
58
|
+
"express": "^4.18.2",
|
|
59
|
+
"body-parser": "^1.20.2",
|
|
60
|
+
"ajv": "^8.12.0"
|
|
61
|
+
},
|
|
62
|
+
"devDependencies": {
|
|
63
|
+
"semantic-release": "^20.1.0",
|
|
64
|
+
"@semantic-release/npm": "^10.0.0",
|
|
65
|
+
"@semantic-release/github": "^9.0.0",
|
|
66
|
+
"@semantic-release/commit-analyzer": "^10.0.0",
|
|
67
|
+
"@semantic-release/release-notes-generator": "^10.0.0",
|
|
68
|
+
"@types/node": "^20.0.0",
|
|
69
|
+
"@types/express": "^4.17.17",
|
|
70
|
+
"@types/body-parser": "^1.19.2",
|
|
71
|
+
"@types/jest": "^29.0.0",
|
|
72
|
+
"typescript": "^5.0.0",
|
|
73
|
+
"jest": "^29.0.0",
|
|
74
|
+
"ts-node": "^10.0.0",
|
|
75
|
+
"ts-jest": "^29.0.0",
|
|
76
|
+
"rimraf": "^5.0.0"
|
|
77
|
+
},
|
|
78
|
+
"engines": {
|
|
79
|
+
"node": ">=18.0.0"
|
|
80
|
+
},
|
|
81
|
+
"publishConfig": {
|
|
82
|
+
"access": "public"
|
|
83
|
+
}
|
|
84
|
+
}
|