@andrebuzeli/git-mcp 10.0.8 → 11.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.
Files changed (135) hide show
  1. package/README.md +34 -428
  2. package/bin/git-mcp.js +21 -0
  3. package/docs/TOOLS.md +110 -0
  4. package/mcp.json.template +12 -0
  5. package/package.json +9 -76
  6. package/src/local/git.js +14 -0
  7. package/src/providers/gitea.js +13 -0
  8. package/src/providers/github.js +13 -0
  9. package/src/server.js +63 -0
  10. package/src/tools/git-actions.js +19 -0
  11. package/src/tools/git-activity.js +28 -0
  12. package/src/tools/git-admin.js +20 -0
  13. package/src/tools/git-checks.js +14 -0
  14. package/src/tools/git-commits.js +34 -0
  15. package/src/tools/git-contents.js +30 -0
  16. package/src/tools/git-deployments.js +21 -0
  17. package/src/tools/git-gists.js +15 -0
  18. package/src/tools/git-gitdata.js +19 -0
  19. package/src/tools/git-issues-prs.js +44 -0
  20. package/src/tools/git-issues.js +12 -0
  21. package/src/tools/git-local.js +66 -0
  22. package/src/tools/git-meta.js +19 -0
  23. package/src/tools/git-misc.js +21 -0
  24. package/src/tools/git-orgs.js +26 -0
  25. package/src/tools/git-packages.js +12 -0
  26. package/src/tools/git-raw.js +14 -0
  27. package/src/tools/git-releases.js +17 -0
  28. package/src/tools/git-remote.js +29 -0
  29. package/src/tools/git-repos.js +60 -0
  30. package/src/tools/git-search.js +18 -0
  31. package/src/tools/git-sync.js +40 -0
  32. package/src/tools/git-user.js +26 -0
  33. package/src/tools/schema.js +3 -0
  34. package/src/utils/fs.js +29 -0
  35. package/src/utils/project.js +7 -0
  36. package/tests/errors.js +26 -0
  37. package/tests/full_suite.js +98 -0
  38. package/tests/run.js +50 -0
  39. package/LICENSE +0 -21
  40. package/dist/index.d.ts +0 -2
  41. package/dist/index.js +0 -224
  42. package/dist/prompts/gitPrompts.d.ts +0 -93
  43. package/dist/prompts/gitPrompts.js +0 -177
  44. package/dist/providers/giteaProvider.d.ts +0 -3
  45. package/dist/providers/giteaProvider.js +0 -6
  46. package/dist/providers/githubProvider.d.ts +0 -2
  47. package/dist/providers/githubProvider.js +0 -4
  48. package/dist/providers/providerManager.d.ts +0 -11
  49. package/dist/providers/providerManager.js +0 -49
  50. package/dist/resources/toolsGuide.d.ts +0 -12
  51. package/dist/resources/toolsGuide.js +0 -1713
  52. package/dist/scripts/test_e2e.d.ts +0 -1
  53. package/dist/scripts/test_e2e.js +0 -199
  54. package/dist/scripts/test_exhaustive.d.ts +0 -1
  55. package/dist/scripts/test_exhaustive.js +0 -275
  56. package/dist/scripts/verify_setup.d.ts +0 -1
  57. package/dist/scripts/verify_setup.js +0 -61
  58. package/dist/server.d.ts +0 -9
  59. package/dist/server.js +0 -73
  60. package/dist/tools/gitAnalytics.d.ts +0 -35
  61. package/dist/tools/gitAnalytics.js +0 -220
  62. package/dist/tools/gitArchive.d.ts +0 -119
  63. package/dist/tools/gitArchive.js +0 -150
  64. package/dist/tools/gitBackup.d.ts +0 -116
  65. package/dist/tools/gitBackup.js +0 -156
  66. package/dist/tools/gitBranches.d.ts +0 -54
  67. package/dist/tools/gitBranches.js +0 -282
  68. package/dist/tools/gitChangelog.d.ts +0 -37
  69. package/dist/tools/gitChangelog.js +0 -67
  70. package/dist/tools/gitConfig.d.ts +0 -97
  71. package/dist/tools/gitConfig.js +0 -125
  72. package/dist/tools/gitFiles.d.ts +0 -129
  73. package/dist/tools/gitFiles.js +0 -213
  74. package/dist/tools/gitFix.d.ts +0 -4
  75. package/dist/tools/gitFix.js +0 -159
  76. package/dist/tools/gitFix.tool.d.ts +0 -31
  77. package/dist/tools/gitFix.tool.js +0 -92
  78. package/dist/tools/gitHistory.d.ts +0 -41
  79. package/dist/tools/gitHistory.js +0 -349
  80. package/dist/tools/gitIgnore.d.ts +0 -214
  81. package/dist/tools/gitIgnore.js +0 -338
  82. package/dist/tools/gitIssues.d.ts +0 -80
  83. package/dist/tools/gitIssues.js +0 -363
  84. package/dist/tools/gitLog.d.ts +0 -30
  85. package/dist/tools/gitLog.js +0 -46
  86. package/dist/tools/gitMonitor.d.ts +0 -30
  87. package/dist/tools/gitMonitor.js +0 -284
  88. package/dist/tools/gitPackages.d.ts +0 -180
  89. package/dist/tools/gitPackages.js +0 -214
  90. package/dist/tools/gitPulls.d.ts +0 -66
  91. package/dist/tools/gitPulls.js +0 -347
  92. package/dist/tools/gitPush.d.ts +0 -40
  93. package/dist/tools/gitPush.js +0 -59
  94. package/dist/tools/gitRelease.d.ts +0 -49
  95. package/dist/tools/gitRelease.js +0 -359
  96. package/dist/tools/gitRemote.d.ts +0 -47
  97. package/dist/tools/gitRemote.js +0 -111
  98. package/dist/tools/gitReset.d.ts +0 -57
  99. package/dist/tools/gitReset.js +0 -79
  100. package/dist/tools/gitStash.d.ts +0 -61
  101. package/dist/tools/gitStash.js +0 -80
  102. package/dist/tools/gitSync.d.ts +0 -34
  103. package/dist/tools/gitSync.js +0 -182
  104. package/dist/tools/gitTags.d.ts +0 -45
  105. package/dist/tools/gitTags.js +0 -251
  106. package/dist/tools/gitUpdate.d.ts +0 -60
  107. package/dist/tools/gitUpdate.js +0 -474
  108. package/dist/tools/gitUpload.d.ts +0 -35
  109. package/dist/tools/gitUpload.js +0 -385
  110. package/dist/tools/gitWorkflow.d.ts +0 -117
  111. package/dist/tools/gitWorkflow.js +0 -472
  112. package/dist/types.d.ts +0 -20
  113. package/dist/types.js +0 -1
  114. package/dist/utils/agentHelpers.d.ts +0 -11
  115. package/dist/utils/agentHelpers.js +0 -41
  116. package/dist/utils/apiHelpers.d.ts +0 -29
  117. package/dist/utils/apiHelpers.js +0 -125
  118. package/dist/utils/cache.d.ts +0 -96
  119. package/dist/utils/cache.js +0 -208
  120. package/dist/utils/contextDetector.d.ts +0 -0
  121. package/dist/utils/contextDetector.js +0 -1
  122. package/dist/utils/errors.d.ts +0 -13
  123. package/dist/utils/errors.js +0 -17
  124. package/dist/utils/gitAdapter.d.ts +0 -224
  125. package/dist/utils/gitAdapter.js +0 -1152
  126. package/dist/utils/logger.d.ts +0 -45
  127. package/dist/utils/logger.js +0 -140
  128. package/dist/utils/rateLimiter.d.ts +0 -113
  129. package/dist/utils/rateLimiter.js +0 -257
  130. package/dist/utils/repoHelpers.d.ts +0 -44
  131. package/dist/utils/repoHelpers.js +0 -122
  132. package/dist/utils/safetyController.d.ts +0 -1
  133. package/dist/utils/safetyController.js +0 -12
  134. package/dist/utils/validation.d.ts +0 -115
  135. package/dist/utils/validation.js +0 -270
package/package.json CHANGED
@@ -1,86 +1,19 @@
1
1
  {
2
2
  "name": "@andrebuzeli/git-mcp",
3
- "version": "10.0.8",
3
+ "version": "11.0.0",
4
4
  "type": "module",
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
- "main": "dist/index.js",
7
- "types": "dist/index.d.ts",
8
5
  "bin": {
9
- "git-mcp": "dist/index.js"
6
+ "git-mcp": "bin/git-mcp.js"
10
7
  },
11
- "files": [
12
- "dist/",
13
- "README.md",
14
- "LICENSE",
15
- "package.json"
16
- ],
17
8
  "scripts": {
18
- "build": "tsc",
19
- "start": "node dist/index.js",
20
- "dev": "ts-node src/index.ts",
21
- "test": "jest",
22
- "test:watch": "jest --watch",
23
- "semantic-release": "semantic-release",
24
- "prepublishOnly": "npm run build",
25
- "clean": "rimraf dist",
26
- "release": "npm run build && npm version patch && git push origin --follow-tags"
9
+ "start": "node bin/git-mcp.js start",
10
+ "init": "node bin/git-mcp.js init",
11
+ "test": "node tests/run.js"
27
12
  },
28
- "keywords": [
29
- "mcp",
30
- "git",
31
- "github",
32
- "gitea",
33
- "universal-mode",
34
- "multi-provider",
35
- "model-context-protocol",
36
- "ai-agent",
37
- "version-control",
38
- "automation",
39
- "cli",
40
- "developer-tools",
41
- "workflow",
42
- "repository-management"
43
- ],
44
- "author": "Andre Buzeli",
45
- "license": "MIT",
46
- "repository": {
47
- "type": "git",
48
- "url": "https://github.com/andrebuzeli/git-mcp.git"
49
- },
50
- "bugs": {
51
- "url": "https://github.com/andrebuzeli/git-mcp/issues"
52
- },
53
- "homepage": "https://github.com/andrebuzeli/git-mcp#readme",
54
13
  "dependencies": {
55
- "@isomorphic-git/lightning-fs": "^4.6.2",
56
- "@modelcontextprotocol/sdk": "^0.4.0",
57
- "@octokit/rest": "^20.0.0",
58
- "ajv": "^8.12.0",
59
- "axios": "^1.6.0",
60
- "body-parser": "^1.20.2",
61
- "express": "^4.18.2",
62
- "isomorphic-git": "^1.34.0"
63
- },
64
- "devDependencies": {
65
- "@semantic-release/commit-analyzer": "^10.0.0",
66
- "@semantic-release/github": "^9.0.0",
67
- "@semantic-release/npm": "^10.0.0",
68
- "@semantic-release/release-notes-generator": "^10.0.0",
69
- "@types/body-parser": "^1.19.2",
70
- "@types/express": "^4.17.17",
71
- "@types/jest": "^29.0.0",
72
- "@types/node": "^20.0.0",
73
- "jest": "^29.0.0",
74
- "rimraf": "^5.0.0",
75
- "semantic-release": "^20.1.0",
76
- "ts-jest": "^29.0.0",
77
- "ts-node": "^10.0.0",
78
- "typescript": "^5.0.0"
79
- },
80
- "engines": {
81
- "node": ">=18.0.0"
82
- },
83
- "publishConfig": {
84
- "access": "public"
14
+ "axios": "^1.7.7",
15
+ "kleur": "^4.1.5",
16
+ "isomorphic-git": "^1.25.4",
17
+ "yargs": "^17.7.2"
85
18
  }
86
19
  }
@@ -0,0 +1,14 @@
1
+ import simpleGit from 'simple-git'
2
+
3
+ export class LocalGit {
4
+ constructor(projectPath) { this.projectPath = projectPath; this.git = simpleGit({ baseDir: projectPath }) }
5
+ async status() { return await this.git.status() }
6
+ async init() { await this.git.init(); return { ok: true } }
7
+ async add(patterns) { await this.git.add(patterns); return { ok: true } }
8
+ async commit(message) { await this.git.commit(message); return { ok: true } }
9
+ async addOrSetRemote(name, url) { const rems = await this.git.getRemotes(true); const exists = rems.find(r => r.name === name); if (exists) { await this.git.remote(['set-url', name, url]) } else { await this.git.addRemote(name, url) } return { ok: true } }
10
+ async push(remote, branch) { await this.git.push(remote, branch); return { ok: true } }
11
+ async pull(remote, branch) { await this.git.pull(remote, branch); return { ok: true } }
12
+ async branch(subaction, args) { if (subaction === 'list') { return await this.git.branchLocal() } if (subaction === 'create') { await this.git.checkoutLocalBranch(args?.name); return { ok: true } } if (subaction === 'switch') { await this.git.checkout(args?.name); return { ok: true } } throw new Error('subaction inválida') }
13
+ async tag(subaction, args) { if (subaction === 'list') { return await this.git.tags() } if (subaction === 'create') { await this.git.addTag(args?.name); return { ok: true } } throw new Error('subaction inválida') }
14
+ }
@@ -0,0 +1,13 @@
1
+ import axios from 'axios'
2
+
3
+ export class Gitea {
4
+ constructor(baseUrl, token) { this.baseUrl = baseUrl; this.token = token; this.http = axios.create({ baseURL: baseUrl + '/api/v1', headers: { Authorization: `token ${token}` } }); this._me = null }
5
+ async me() { if (this._me) return this._me; const r = await this.http.get('/user'); this._me = r.data; return r.data }
6
+ async request(method, endpoint, data) { const r = await this.http.request({ method, url: endpoint, data }); return r.data }
7
+ async createRepo(name, priv, auto) { const r = await this.request('POST', '/user/repos', { name, private: priv, auto_init: auto }); return r }
8
+ async deleteRepo(name) { const me = await this.me(); await this.request('DELETE', `/repos/${me.login}/${name}`); return { ok: true } }
9
+ repoHttpsUrl(name) { return this.me().then(me => `${this.baseUrl}/${me.login}/${name}.git`) }
10
+ async createIssue(name, title, body) { const me = await this.me(); const r = await this.request('POST', `/repos/${me.login}/${name}/issues`, { title, body }); return r }
11
+ async createRelease(name, tag, title, body) { const me = await this.me(); const r = await this.request('POST', `/repos/${me.login}/${name}/releases`, { tag_name: tag, name: title, body }); return r }
12
+ async putContent(name, path, content, message, branch) { const me = await this.me(); const r = await this.request('POST', `/repos/${me.login}/${name}/contents/${path}`, { content, message, branch: branch || 'master' }); return r }
13
+ }
@@ -0,0 +1,13 @@
1
+ import axios from 'axios'
2
+
3
+ export class Github {
4
+ constructor(token) { this.http = axios.create({ baseURL: 'https://api.github.com', headers: { Authorization: `token ${token}`, Accept: 'application/vnd.github.v3+json', 'User-Agent': 'git-mcp' } }); this._me = null }
5
+ async me() { if (this._me) return this._me; const r = await this.http.get('/user'); this._me = r.data; return r.data }
6
+ async request(method, endpoint, data) { const r = await this.http.request({ method, url: endpoint, data }); return r.data }
7
+ async createRepo(name, priv, auto) { const r = await this.request('POST', '/user/repos', { name, private: priv, auto_init: auto }); return r }
8
+ async deleteRepo(name) { const me = await this.me(); await this.request('DELETE', `/repos/${me.login}/${name}`); return { ok: true } }
9
+ repoHttpsUrl(name) { return this.me().then(me => `https://github.com/${me.login}/${name}.git`) }
10
+ async createIssue(name, title, body) { const me = await this.me(); const r = await this.request('POST', `/repos/${me.login}/${name}/issues`, { title, body }); return r }
11
+ async createRelease(name, tag, title, body) { const me = await this.me(); const r = await this.request('POST', `/repos/${me.login}/${name}/releases`, { tag_name: tag, name: title, body, prerelease: false, draft: false }); return r }
12
+ async putContent(name, path, content, message, branch) { const me = await this.me(); const r = await this.request('PUT', `/repos/${me.login}/${name}/contents/${path}`, { content, message, branch: branch || 'master' }); return r }
13
+ }
package/src/server.js ADDED
@@ -0,0 +1,63 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import { fileURLToPath } from 'url'
4
+ import { deriveProjectName } from './utils/project.js'
5
+
6
+ export async function initMcpJson(targetPath, force) {
7
+ const base = targetPath || process.cwd()
8
+ const file = path.join(base, 'mcp.json')
9
+ const templatePath = path.join(path.dirname(fileURLToPath(import.meta.url)), '..', 'mcp.json.template')
10
+ const tpl = fs.readFileSync(templatePath, 'utf-8')
11
+ if (!force && fs.existsSync(file)) { console.log('mcp.json já existe'); return }
12
+ fs.writeFileSync(file, tpl, 'utf-8')
13
+ console.log('mcp.json criado')
14
+ }
15
+
16
+ function getEnv() { return { GITEA_URL: process.env.GITEA_URL, GITEA_TOKEN: process.env.GITEA_TOKEN, GITHUB_TOKEN: process.env.GITHUB_TOKEN } }
17
+
18
+ async function getTool(toolName, env) {
19
+ if (toolName === 'git-local') { const { GitLocalTool } = await import('./tools/git-local.js'); return new GitLocalTool() }
20
+ if (toolName === 'git-remote') { const { GitRemoteTool } = await import('./tools/git-remote.js'); return new GitRemoteTool(env) }
21
+ if (toolName === 'git-sync') { const { GitSyncTool } = await import('./tools/git-sync.js'); return new GitSyncTool(env) }
22
+ if (toolName === 'git-issues') { const { GitIssuesTool } = await import('./tools/git-issues.js'); return new GitIssuesTool(env) }
23
+ if (toolName === 'git-releases') { const { GitReleasesTool } = await import('./tools/git-releases.js'); return new GitReleasesTool(env) }
24
+ if (toolName === 'git-repos') { const { GitReposTool } = await import('./tools/git-repos.js'); return new GitReposTool(env) }
25
+ if (toolName === 'git-contents') { const { GitContentsTool } = await import('./tools/git-contents.js'); return new GitContentsTool(env) }
26
+ if (toolName === 'git-commits') { const { GitCommitsTool } = await import('./tools/git-commits.js'); return new GitCommitsTool(env) }
27
+ if (toolName === 'git-issues-prs') { const { GitIssuesPRsTool } = await import('./tools/git-issues-prs.js'); return new GitIssuesPRsTool(env) }
28
+ if (toolName === 'git-actions') { const { GitActionsTool } = await import('./tools/git-actions.js'); return new GitActionsTool(env) }
29
+ if (toolName === 'git-activity') { const { GitActivityTool } = await import('./tools/git-activity.js'); return new GitActivityTool(env) }
30
+ if (toolName === 'git-admin') { const { GitAdminTool } = await import('./tools/git-admin.js'); return new GitAdminTool(env) }
31
+ if (toolName === 'git-user') { const { GitUserTool } = await import('./tools/git-user.js'); return new GitUserTool(env) }
32
+ if (toolName === 'git-orgs') { const { GitOrgsTool } = await import('./tools/git-orgs.js'); return new GitOrgsTool(env) }
33
+ if (toolName === 'git-gists') { const { GitGistsTool } = await import('./tools/git-gists.js'); return new GitGistsTool(env) }
34
+ if (toolName === 'git-search') { const { GitSearchTool } = await import('./tools/git-search.js'); return new GitSearchTool(env) }
35
+ if (toolName === 'git-gitdata') { const { GitGitDataTool } = await import('./tools/git-gitdata.js'); return new GitGitDataTool(env) }
36
+ if (toolName === 'git-checks') { const { GitChecksTool } = await import('./tools/git-checks.js'); return new GitChecksTool(env) }
37
+ if (toolName === 'git-deployments') { const { GitDeploymentsTool } = await import('./tools/git-deployments.js'); return new GitDeploymentsTool(env) }
38
+ if (toolName === 'git-misc') { const { GitMiscTool } = await import('./tools/git-misc.js'); return new GitMiscTool(env) }
39
+ if (toolName === 'git-packages') { const { GitPackagesTool } = await import('./tools/git-packages.js'); return new GitPackagesTool(env) }
40
+ if (toolName === 'git-raw') { const { GitRawTool } = await import('./tools/git-raw.js'); return new GitRawTool(env) }
41
+ if (toolName === 'git-meta') { const { GitMetaTool } = await import('./tools/git-meta.js'); return new GitMetaTool(env) }
42
+ return null
43
+ }
44
+
45
+ async function dispatch(req) {
46
+ const { tool, action, projectPath, args } = req
47
+ const name = deriveProjectName(projectPath)
48
+ const inst = await getTool(tool, getEnv())
49
+ if (inst) return await inst.handle(action, projectPath, args, getEnv())
50
+ throw new Error('Tool/action não suportado')
51
+ }
52
+
53
+ export async function startServer() {
54
+ process.stdin.setEncoding('utf-8')
55
+ let buf = ''
56
+ process.stdin.on('data', async chunk => {
57
+ buf += chunk
58
+ try { const req = JSON.parse(buf); buf = ''; const res = await dispatch(req); process.stdout.write(JSON.stringify({ ok: true, res }) + '\n') } catch (e) { if (e instanceof SyntaxError) return; process.stdout.write(JSON.stringify({ ok: false, error: e.message }) + '\n'); buf = '' }
59
+ })
60
+ console.log('git-mcp pronto')
61
+ }
62
+
63
+ export async function callOnce(req) { return await dispatch(req) }
@@ -0,0 +1,19 @@
1
+ import { Github } from '../providers/github.js'
2
+ import { makeSchema } from './schema.js'
3
+
4
+ export class GitActionsTool {
5
+ constructor(env) { this.gh = new Github(env.GITHUB_TOKEN) }
6
+ async handle(action, projectPath, args) {
7
+ const me = await this.gh.me(); const name = projectPath.split(/[\\/]/).pop().replace(/\s+/g, '_'); const repo = `${me.login}/${name}`
8
+ if (action === 'workflows') return await this.gh.request('GET', `/repos/${repo}/actions/workflows`)
9
+ if (action === 'runs') return await this.gh.request('GET', `/repos/${repo}/actions/runs`)
10
+ if (action === 'artifacts') return await this.gh.request('GET', `/repos/${repo}/actions/artifacts`)
11
+ if (action === 'runners') return await this.gh.request('GET', `/repos/${repo}/actions/runners`)
12
+ if (action === 'secrets') return await this.gh.request('GET', `/repos/${repo}/actions/secrets`)
13
+ return { error: 'ação inválida' }
14
+ }
15
+ }
16
+
17
+ export function getSchema() {
18
+ return makeSchema({ name: 'git-actions', description: 'Consultas a GitHub Actions', actions: { workflows: { description: 'Listar workflows' }, runs: { description: 'Listar runs' }, artifacts: { description: 'Listar artifacts' }, runners: { description: 'Listar runners' }, secrets: { description: 'Listar secrets' } } })
19
+ }
@@ -0,0 +1,28 @@
1
+ import { Gitea } from '../providers/gitea.js'
2
+ import { Github } from '../providers/github.js'
3
+ import { deriveProjectName } from '../utils/project.js'
4
+ import { makeSchema } from './schema.js'
5
+
6
+ export class GitActivityTool {
7
+ constructor(env) { this.providers = { gitea: new Gitea(env.GITEA_URL, env.GITEA_TOKEN), github: new Github(env.GITHUB_TOKEN) } }
8
+ async handle(action, projectPath, args) {
9
+ const name = deriveProjectName(projectPath)
10
+ const meG = await this.providers.gitea.me(); const meH = await this.providers.github.me()
11
+ const gRepo = `${meG.login}/${name}`; const hRepo = `${meH.login}/${name}`
12
+ const par = async (fnG, fnH) => { const r = await Promise.allSettled([ fnG(), fnH() ]); return r.map(x => x.status === 'fulfilled' ? x.value : { error: x.reason?.message }) }
13
+ if (action === 'notifications-get') return await par(() => this.providers.gitea.request('GET', '/notifications'), () => this.providers.github.request('GET', '/notifications'))
14
+ if (action === 'notifications-put') return await par(() => this.providers.gitea.request('PUT', '/notifications', { last_read_at: Date.now() / 1000 }), () => ({ ok: true }))
15
+ if (action === 'events') return await par(() => this.providers.gitea.request('GET', `/repos/${gRepo}/activities/feeds`), () => this.providers.github.request('GET', '/events'))
16
+ if (action === 'feeds') return await this.providers.github.request('GET', '/feeds')
17
+ if (action === 'starring-put') return await this.providers.github.request('PUT', `/user/starred/${hRepo}`)
18
+ if (action === 'starring-get') return await this.providers.github.request('GET', `/user/starred/${hRepo}`)
19
+ if (action === 'starring-delete') return await this.providers.github.request('DELETE', `/user/starred/${hRepo}`)
20
+ if (action === 'subscription-put') return await par(() => this.providers.gitea.request('PUT', `/repos/${gRepo}/subscription`, { subscribed: true }), () => this.providers.github.request('PUT', `/repos/${hRepo}/subscription`, { subscribed: true }))
21
+ if (action === 'subscription-delete') return await par(() => this.providers.gitea.request('DELETE', `/repos/${gRepo}/subscription`), () => this.providers.github.request('DELETE', `/repos/${hRepo}/subscription`))
22
+ return { error: 'ação inválida' }
23
+ }
24
+ }
25
+
26
+ export function getSchema() {
27
+ return makeSchema({ name: 'git-activity', description: 'Notificações e atividades', actions: { 'notifications-get': { description: 'Obter notificações' }, 'notifications-put': { description: 'Marcar como lidas' }, events: { description: 'Eventos' }, feeds: { description: 'Feeds (GitHub)' }, 'starring-put': { description: 'Estrelas' }, 'starring-get': { description: 'Checar estrela' }, 'starring-delete': { description: 'Remover estrela' }, 'subscription-put': { description: 'Assinar repo' }, 'subscription-delete': { description: 'Cancelar assinatura' } } })
28
+ }
@@ -0,0 +1,20 @@
1
+ import { Gitea } from '../providers/gitea.js'
2
+ import { makeSchema } from './schema.js'
3
+
4
+ export class GitAdminTool {
5
+ constructor(env) { this.gitea = new Gitea(env.GITEA_URL, env.GITEA_TOKEN) }
6
+ async handle(action, projectPath, args) {
7
+ if (action === 'cron') return await this.gitea.request('GET', '/admin/cron')
8
+ if (action === 'emails') return await this.gitea.request('GET', '/admin/emails')
9
+ if (action === 'orgs') return await this.gitea.request('GET', '/admin/orgs')
10
+ if (action === 'users') return await this.gitea.request('GET', '/admin/users')
11
+ if (action === 'user-create') return await this.gitea.request('POST', '/admin/users', { username: args?.username, email: args?.email, password: args?.password || 'Password123!', must_change_password: false })
12
+ if (action === 'user-patch') return await this.gitea.request('PATCH', `/admin/users/${args?.username}`, { full_name: args?.full_name || 'Updated Name', login_name: args?.username, email: args?.email })
13
+ if (action === 'user-delete') return await this.gitea.request('DELETE', `/admin/users/${args?.username}`)
14
+ if (action === 'unadopted') return await this.gitea.request('GET', '/admin/unadopted')
15
+ if (action === 'hooks') return await this.gitea.request('GET', '/admin/hooks')
16
+ return { error: 'ação inválida' }
17
+ }
18
+ }
19
+
20
+ export function getSchema() { return makeSchema({ name: 'git-admin', description: 'Endpoints de administração (Gitea)', actions: { cron: { description: 'Cron jobs' }, emails: { description: 'Emails' }, orgs: { description: 'Organizações' }, users: { description: 'Usuários' }, 'user-create': { description: 'Criar usuário' }, 'user-patch': { description: 'Atualizar usuário' }, 'user-delete': { description: 'Excluir usuário' }, unadopted: { description: 'Repositórios não adotados' }, hooks: { description: 'Hooks admin' } } }) }
@@ -0,0 +1,14 @@
1
+ import { Github } from '../providers/github.js'
2
+ import { deriveProjectName } from '../utils/project.js'
3
+ import { makeSchema } from './schema.js'
4
+
5
+ export class GitChecksTool {
6
+ constructor(env) { this.gh = new Github(env.GITHUB_TOKEN) }
7
+ async handle(action, projectPath, args) {
8
+ const me = await this.gh.me(); const name = deriveProjectName(projectPath); const repo = `${me.login}/${name}`
9
+ if (action === 'status') return await this.gh.request('POST', `/repos/${repo}/statuses/${args?.sha}`, { state: args?.state || 'success', context: args?.context || 'test' })
10
+ return { error: 'ação inválida' }
11
+ }
12
+ }
13
+
14
+ export function getSchema() { return makeSchema({ name: 'git-checks', description: 'Statuses/Checks (GitHub)', actions: { status: { description: 'Definir status para SHA' } } }) }
@@ -0,0 +1,34 @@
1
+ import { Gitea } from '../providers/gitea.js'
2
+ import { Github } from '../providers/github.js'
3
+ import { deriveProjectName } from '../utils/project.js'
4
+ import { makeSchema } from './schema.js'
5
+
6
+ export class GitCommitsTool {
7
+ constructor(env) { this.providers = { gitea: new Gitea(env.GITEA_URL, env.GITEA_TOKEN), github: new Github(env.GITHUB_TOKEN) } }
8
+ async handle(action, projectPath, args) {
9
+ const name = deriveProjectName(projectPath)
10
+ const meG = await this.providers.gitea.me(); const meH = await this.providers.github.me()
11
+ const gRepo = `${meG.login}/${name}`; const hRepo = `${meH.login}/${name}`
12
+ const par = async (fnG, fnH) => { const r = await Promise.allSettled([ fnG(), fnH() ]); return r.map(x => x.status === 'fulfilled' ? x.value : { error: x.reason?.message }) }
13
+ if (action === 'branches') return await par(() => this.providers.gitea.request('GET', `/repos/${gRepo}/branches`), () => this.providers.github.request('GET', `/repos/${hRepo}/branches`))
14
+ if (action === 'create-branch') return await par(() => this.providers.gitea.request('POST', `/repos/${gRepo}/branches`, { new_branch_name: args?.name || 'dev', old_branch_name: args?.from || 'main' }), async () => { const ref = await this.providers.github.request('GET', `/repos/${hRepo}/git/ref/heads/${args?.from || 'main'}`); const sha = ref.object.sha; return await this.providers.github.request('POST', `/repos/${hRepo}/git/refs`, { ref: `refs/heads/${args?.name || 'dev'}`, sha }) })
15
+ if (action === 'branch-protections') return await par(() => this.providers.gitea.request('GET', `/repos/${gRepo}/branch_protections`), () => ({ ok: true }))
16
+ if (action === 'commits') return await par(() => this.providers.gitea.request('GET', `/repos/${gRepo}/commits`), () => this.providers.github.request('GET', `/repos/${hRepo}/commits`))
17
+ if (action === 'refs') return await par(() => this.providers.gitea.request('GET', `/repos/${gRepo}/git/refs`), () => this.providers.github.request('GET', `/repos/${hRepo}/git/refs`))
18
+ return { error: 'ação inválida' }
19
+ }
20
+ }
21
+
22
+ export function getSchema() {
23
+ return makeSchema({
24
+ name: 'git-commits',
25
+ description: 'Branches, commits e refs',
26
+ actions: {
27
+ branches: { description: 'Listar branches' },
28
+ 'create-branch': { description: 'Criar branch' },
29
+ 'branch-protections': { description: 'Proteções de branch (Gitea)' },
30
+ commits: { description: 'Listar commits' },
31
+ refs: { description: 'Listar refs' }
32
+ }
33
+ })
34
+ }
@@ -0,0 +1,30 @@
1
+ import { Gitea } from '../providers/gitea.js'
2
+ import { Github } from '../providers/github.js'
3
+ import { deriveProjectName } from '../utils/project.js'
4
+ import { makeSchema } from './schema.js'
5
+
6
+ export class GitContentsTool {
7
+ constructor(env) { this.providers = { gitea: new Gitea(env.GITEA_URL, env.GITEA_TOKEN), github: new Github(env.GITHUB_TOKEN) } }
8
+ async handle(action, projectPath, args) {
9
+ const name = deriveProjectName(projectPath)
10
+ const meG = await this.providers.gitea.me(); const meH = await this.providers.github.me()
11
+ const gRepo = `${meG.login}/${name}`; const hRepo = `${meH.login}/${name}`
12
+ const par = async (fnG, fnH) => { const r = await Promise.allSettled([ fnG(), fnH() ]); return r.map(x => x.status === 'fulfilled' ? x.value : { error: x.reason?.message }) }
13
+ if (action === 'list') return await par(() => this.providers.gitea.request('GET', `/repos/${gRepo}/contents`), () => this.providers.github.request('GET', `/repos/${hRepo}/readme`))
14
+ if (action === 'create') { const path = args?.path || 'test.txt'; const content = args?.content || Buffer.from('Hello').toString('base64'); const message = args?.message || 'Add file'; return await par(() => this.providers.gitea.request('POST', `/repos/${gRepo}/contents/${path}`, { content, message, branch: args?.branch || 'main' }), () => this.providers.github.request('PUT', `/repos/${hRepo}/contents/${path}`, { content, message, branch: args?.branch || 'main' })) }
15
+ if (action === 'get') { const path = args?.path || 'test.txt'; return await par(() => this.providers.gitea.request('GET', `/repos/${gRepo}/contents/${path}`), () => this.providers.github.request('GET', `/repos/${hRepo}/contents/${path}`)) }
16
+ return { error: 'ação inválida' }
17
+ }
18
+ }
19
+
20
+ export function getSchema() {
21
+ return makeSchema({
22
+ name: 'git-contents',
23
+ description: 'Listar/criar/obter arquivos via Contents API',
24
+ actions: {
25
+ list: { description: 'Listar conteúdos' },
26
+ create: { description: 'Criar arquivo (base64)' },
27
+ get: { description: 'Obter arquivo' }
28
+ }
29
+ })
30
+ }
@@ -0,0 +1,21 @@
1
+ import { Github } from '../providers/github.js'
2
+ import { deriveProjectName } from '../utils/project.js'
3
+ import { makeSchema } from './schema.js'
4
+
5
+ export class GitDeploymentsTool {
6
+ constructor(env) { this.gh = new Github(env.GITHUB_TOKEN) }
7
+ async handle(action, projectPath, args) {
8
+ const me = await this.gh.me(); const name = deriveProjectName(projectPath); const repo = `${me.login}/${name}`
9
+ if (action === 'create') {
10
+ let ref = args?.ref
11
+ if (!ref) {
12
+ try { const branches = await this.gh.request('GET', `/repos/${repo}/branches`); const names = (branches || []).map(b => b.name); ref = names.includes('main') ? 'main' : (names.includes('master') ? 'master' : (names[0] || 'main')) } catch { ref = 'main' }
13
+ }
14
+ return await this.gh.request('POST', `/repos/${repo}/deployments`, { ref })
15
+ }
16
+ if (action === 'status') return await this.gh.request('POST', `/repos/${repo}/deployments/${args?.id}/statuses`, { state: args?.state || 'success' })
17
+ return { error: 'ação inválida' }
18
+ }
19
+ }
20
+
21
+ export function getSchema() { return makeSchema({ name: 'git-deployments', description: 'Deployments (GitHub)', actions: { create: { description: 'Criar deployment (ref automática)' }, status: { description: 'Atualizar status do deployment' } } }) }
@@ -0,0 +1,15 @@
1
+ import { Github } from '../providers/github.js'
2
+ import { makeSchema } from './schema.js'
3
+
4
+ export class GitGistsTool {
5
+ constructor(env) { this.gh = new Github(env.GITHUB_TOKEN) }
6
+ async handle(action, projectPath, args) {
7
+ if (action === 'create') return await this.gh.request('POST', '/gists', { description: args?.description || 'Test Gist', public: args?.public ?? false, files: args?.files || { 'test.txt': { content: 'Gist Content' } } })
8
+ if (action === 'get') return await this.gh.request('GET', `/gists/${args?.id}`)
9
+ if (action === 'star') return await this.gh.request('PUT', `/gists/${args?.id}/star`)
10
+ if (action === 'delete') return await this.gh.request('DELETE', `/gists/${args?.id}`)
11
+ return { error: 'ação inválida' }
12
+ }
13
+ }
14
+
15
+ export function getSchema() { return makeSchema({ name: 'git-gists', description: 'Gerenciar gists (GitHub)', actions: { create: { description: 'Criar gist' }, get: { description: 'Obter gist' }, star: { description: 'Favoritar gist' }, delete: { description: 'Excluir gist' } } }) }
@@ -0,0 +1,19 @@
1
+ import { Github } from '../providers/github.js'
2
+ import { deriveProjectName } from '../utils/project.js'
3
+ import { makeSchema } from './schema.js'
4
+
5
+ export class GitGitDataTool {
6
+ constructor(env) { this.gh = new Github(env.GITHUB_TOKEN) }
7
+ async handle(action, projectPath, args) {
8
+ const me = await this.gh.me(); const name = deriveProjectName(projectPath); const repo = `${me.login}/${name}`
9
+ if (action === 'blob-create') return await this.gh.request('POST', `/repos/${repo}/git/blobs`, { content: args?.content || 'Hello World', encoding: args?.encoding || 'utf-8' })
10
+ if (action === 'blob-get') return await this.gh.request('GET', `/repos/${repo}/git/blobs/${args?.sha}`)
11
+ if (action === 'tree-create') return await this.gh.request('POST', `/repos/${repo}/git/trees`, { tree: args?.tree || [] })
12
+ if (action === 'refs-get') return await this.gh.request('GET', `/repos/${repo}/git/refs`)
13
+ return { error: 'ação inválida' }
14
+ }
15
+ }
16
+
17
+ export function getSchema() {
18
+ return makeSchema({ name: 'git-gitdata', description: 'Dados Git low-level (GitHub)', actions: { 'blob-create': { description: 'Criar blob' }, 'blob-get': { description: 'Obter blob' }, 'tree-create': { description: 'Criar tree' }, 'refs-get': { description: 'Listar refs' } } })
19
+ }
@@ -0,0 +1,44 @@
1
+ import { Gitea } from '../providers/gitea.js'
2
+ import { Github } from '../providers/github.js'
3
+ import { deriveProjectName } from '../utils/project.js'
4
+ import { makeSchema } from './schema.js'
5
+
6
+ export class GitIssuesPRsTool {
7
+ constructor(env) { this.providers = { gitea: new Gitea(env.GITEA_URL, env.GITEA_TOKEN), github: new Github(env.GITHUB_TOKEN) } }
8
+ async handle(action, projectPath, args) {
9
+ const name = deriveProjectName(projectPath)
10
+ const meG = await this.providers.gitea.me(); const meH = await this.providers.github.me()
11
+ const gRepo = `${meG.login}/${name}`; const hRepo = `${meH.login}/${name}`
12
+ const par = async (fnG, fnH) => { const r = await Promise.allSettled([ fnG(), fnH() ]); return r.map(x => x.status === 'fulfilled' ? x.value : { error: x.reason?.message }) }
13
+ if (action === 'issue-create') return await par(() => this.providers.gitea.request('POST', `/repos/${gRepo}/issues`, { title: args?.title || 'Test Issue', body: args?.body || '' }), () => this.providers.github.request('POST', `/repos/${hRepo}/issues`, { title: args?.title || 'Test Issue', body: args?.body || '' }))
14
+ if (action === 'issue-comment') { const id = args?.number; return await par(() => this.providers.gitea.request('POST', `/repos/${gRepo}/issues/${id}/comments`, { body: args?.comment || 'Comment' }), () => this.providers.github.request('POST', `/repos/${hRepo}/issues/${id}/comments`, { body: args?.comment || 'Comment' })) }
15
+ if (action === 'issue-labels') { const id = args?.number; try { return await par(() => this.providers.gitea.request('POST', `/repos/${gRepo}/issues/${id}/labels`, { labels: args?.labels || [1] }), () => this.providers.github.request('POST', `/repos/${hRepo}/issues/${id}/labels`, { labels: args?.labels || ['bug'] })) } catch (e) { try { await this.providers.github.request('POST', `/repos/${hRepo}/labels`, { name: (args?.labels && args.labels[0]) || 'bug', color: '#ff0000' }); return await this.providers.github.request('POST', `/repos/${hRepo}/issues/${id}/labels`, { labels: args?.labels || ['bug'] }) } catch { return { error: 'labels failed' } } } }
16
+ if (action === 'issue-lock') { const id = args?.number; return await par(() => ({ ok: true }), () => this.providers.github.request('PUT', `/repos/${hRepo}/issues/${id}/lock`)) }
17
+ if (action === 'issue-unlock') { const id = args?.number; return await par(() => ({ ok: true }), () => this.providers.github.request('DELETE', `/repos/${hRepo}/issues/${id}/lock`)) }
18
+ if (action === 'labels-list') return await par(() => this.providers.gitea.request('GET', `/repos/${gRepo}/labels`), () => this.providers.github.request('GET', `/repos/${hRepo}/labels`))
19
+ if (action === 'labels-create') return await par(() => this.providers.gitea.request('POST', `/repos/${gRepo}/labels`, { name: args?.name || 'bug', color: args?.color || 'ff0000' }), () => this.providers.github.request('POST', `/repos/${hRepo}/labels`, { name: args?.name || 'bug', color: '#' + (args?.color || 'ff0000') }))
20
+ if (action === 'milestones-list') return await par(() => this.providers.gitea.request('GET', `/repos/${gRepo}/milestones`), () => this.providers.github.request('GET', `/repos/${hRepo}/milestones`))
21
+ if (action === 'milestones-create') return await par(() => this.providers.gitea.request('POST', `/repos/${gRepo}/milestones`, { title: args?.title || 'v1.0' }), () => this.providers.github.request('POST', `/repos/${hRepo}/milestones`, { title: args?.title || 'v2.0' }))
22
+ if (action === 'pr-create') { try { return await par(() => this.providers.gitea.request('POST', `/repos/${gRepo}/pulls`, { title: args?.title || 'PR', head: args?.head || 'dev', base: args?.base || 'main' }), () => this.providers.github.request('POST', `/repos/${hRepo}/pulls`, { title: args?.title || 'PR', head: args?.head || 'feature-branch', base: args?.base || 'main' })) } catch (e) { try { const ref = await this.providers.github.request('GET', `/repos/${hRepo}/git/ref/heads/${args?.base || 'main'}`); const sha = ref.object.sha; await this.providers.github.request('POST', `/repos/${hRepo}/git/refs`, { ref: `refs/heads/${args?.head || 'feature-branch'}`, sha }); await this.providers.github.putContent(name, 'feature.txt', Buffer.from('Feature Change').toString('base64'), 'Feature', args?.head || 'feature-branch'); return await this.providers.github.request('POST', `/repos/${hRepo}/pulls`, { title: args?.title || 'PR', head: args?.head || 'feature-branch', base: args?.base || 'main' }) } catch { return { error: 'pr failed' } } } }
23
+ if (action === 'pr-get') { const num = args?.number; return await par(() => this.providers.gitea.request('GET', `/repos/${gRepo}/pulls/${num}`), () => this.providers.github.request('GET', `/repos/${hRepo}/pulls/${num}`)) }
24
+ return { error: 'ação inválida' }
25
+ }
26
+ }
27
+
28
+ export function getSchema() {
29
+ return makeSchema({
30
+ name: 'git-issues-prs',
31
+ description: 'Issues, comentários, labels, milestones e pull requests',
32
+ actions: {
33
+ 'issue-create': { description: 'Criar issue' },
34
+ 'issue-comment': { description: 'Comentar issue' },
35
+ 'issue-labels': { description: 'Aplicar labels à issue (autocorreção)' },
36
+ 'labels-list': { description: 'Listar labels' },
37
+ 'labels-create': { description: 'Criar label' },
38
+ 'milestones-list': { description: 'Listar milestones' },
39
+ 'milestones-create': { description: 'Criar milestone' },
40
+ 'pr-create': { description: 'Criar PR (autocorreção de branch)' },
41
+ 'pr-get': { description: 'Obter PR' }
42
+ }
43
+ })
44
+ }
@@ -0,0 +1,12 @@
1
+ import { Gitea } from '../providers/gitea.js'
2
+ import { Github } from '../providers/github.js'
3
+ import { deriveProjectName } from '../utils/project.js'
4
+
5
+ export class GitIssuesTool {
6
+ constructor(env) { this.providers = { gitea: new Gitea(env.GITEA_URL, env.GITEA_TOKEN), github: new Github(env.GITHUB_TOKEN) } }
7
+ async handle(action, projectPath, args) {
8
+ const name = deriveProjectName(projectPath)
9
+ if (action === 'create') { const r = await Promise.allSettled([ this.providers.gitea.createIssue(name, args?.title || 'Issue', args?.body || ''), this.providers.github.createIssue(name, args?.title || 'Issue', args?.body || '' ) ]); return r.map(x => x.status === 'fulfilled' ? x.value : { error: x.reason?.message }) }
10
+ return { error: 'ação inválida' }
11
+ }
12
+ }
@@ -0,0 +1,66 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+ import git from 'isomorphic-git'
4
+ import http from 'isomorphic-git/http/node'
5
+ import { deriveProjectName } from '../utils/project.js'
6
+ import { Github } from '../providers/github.js'
7
+ import { Gitea } from '../providers/gitea.js'
8
+ import { makeSchema } from './schema.js'
9
+
10
+ async function addAll(dir) {
11
+ const status = await git.statusMatrix({ fs, dir })
12
+ for (const row of status) {
13
+ const filepath = row[0]
14
+ const workdir = row[1]
15
+ const stage = row[2]
16
+ if (workdir !== stage) await git.add({ fs, dir, filepath })
17
+ }
18
+ return { ok: true }
19
+ }
20
+
21
+ export class GitLocalTool {
22
+ async handle(action, projectPath, args, env) {
23
+ const dir = projectPath
24
+ if (action === 'status') {
25
+ const s = await git.statusMatrix({ fs, dir })
26
+ return { files: s.length }
27
+ }
28
+ if (action === 'init') { await git.init({ fs, dir }); return { ok: true } }
29
+ if (action === 'add') { if (args?.patterns) { for (const p of args.patterns) { const full = path.join(dir, p); if (fs.existsSync(full) && fs.statSync(full).isFile()) await git.add({ fs, dir, filepath: p }) } return { ok: true } } return await addAll(dir) }
30
+ if (action === 'commit') { const author = { name: args?.name || 'mcp', email: args?.email || 'mcp@example.com' }; const sha = await git.commit({ fs, dir, message: args?.message || 'mcp', author }); return { sha } }
31
+ if (action === 'branch') { const sub = args?.subaction || 'list'; if (sub === 'list') { const list = await git.listBranches({ fs, dir }); return { branches: list } } if (sub === 'create') { await git.branch({ fs, dir, ref: args?.name }); return { ok: true } } if (sub === 'switch') { await git.checkout({ fs, dir, ref: args?.name }); return { ok: true } } }
32
+ if (action === 'tag') { const sub = args?.subaction || 'list'; if (sub === 'list') { const list = await git.listTags({ fs, dir }); return { tags: list } } if (sub === 'create') { await git.tag({ fs, dir, ref: args?.name }); return { ok: true } } }
33
+ if (action === 'addRemote') { await git.addRemote({ fs, dir, remote: args?.name, url: args?.url }); return { ok: true } }
34
+ if (action === 'push') { const remote = args?.remote; const url = args?.url; const ref = args?.branch || 'master'; let ghUser, gtUser; try { if (env.GITHUB_TOKEN) { ghUser = (await new Github(env.GITHUB_TOKEN).me()).login } } catch {} ; try { if (env.GITEA_URL && env.GITEA_TOKEN) { gtUser = (await new Gitea(env.GITEA_URL, env.GITEA_TOKEN).me()).login } } catch {} ; const onAuth = ({ url }) => { if (!url) return {}; if (url.includes('github.com')) return { username: ghUser || 'username', password: env.GITHUB_TOKEN }; if (env.GITEA_URL && url.includes(new URL(env.GITEA_URL).host)) return { username: gtUser || 'username', password: env.GITEA_TOKEN }; return {} } ; const opts = { fs, http, dir, ref, onAuth }; if (remote) opts.remote = remote; if (url) opts.url = url; if (!remote && !url) { const origin = await git.getConfig({ fs, dir, path: 'remote.origin.url' }); if (origin) { opts.remote = 'origin' } else { throw new Error('Remote ou URL não fornecido e nenhum remote.origin.url configurado') } } try { await git.push(opts); return { ok: true } } catch (e) { // fallback: conteúdo via API
35
+ const name = deriveProjectName(dir); const files = (await fs.promises.readdir(dir)).filter(f => f !== '.git')
36
+ const gitea = env.GITEA_URL && env.GITEA_TOKEN ? new Gitea(env.GITEA_URL, env.GITEA_TOKEN) : null
37
+ const github = env.GITHUB_TOKEN ? new Github(env.GITHUB_TOKEN) : null
38
+ for (const f of files) {
39
+ const p = path.join(dir, f); if (fs.statSync(p).isFile()) { const content = fs.readFileSync(p).toString('base64'); try { if (gitea) await gitea.putContent(name, f, content, 'mcp push fallback', ref) } catch {} ; try { if (github) await github.putContent(name, f, content, 'mcp push fallback', ref) } catch {} }
40
+ }
41
+ return { ok: true, fallback: true }
42
+ }
43
+ }
44
+ if (action === 'pull') { const remote = args?.remote; const url = args?.url; const ref = args?.branch || 'master'; let ghUser, gtUser; try { if (env.GITHUB_TOKEN) { ghUser = (await new Github(env.GITHUB_TOKEN).me()).login } } catch {} ; try { if (env.GITEA_URL && env.GITEA_TOKEN) { gtUser = (await new Gitea(env.GITEA_URL, env.GITEA_TOKEN).me()).login } } catch {} ; const onAuth = ({ url }) => { if (!url) return {}; if (url.includes('github.com')) return { username: ghUser || 'git', password: env.GITHUB_TOKEN }; if (env.GITEA_URL && url.includes(new URL(env.GITEA_URL).host)) return { username: gtUser || 'git', password: env.GITEA_TOKEN }; return {} } ; const opts = { fs, http, dir, ref, singleBranch: true, onAuth }; if (remote) opts.remote = remote; if (url) opts.url = url; if (!remote && !url) { const origin = await git.getConfig({ fs, dir, path: 'remote.origin.url' }); if (origin) { opts.remote = 'origin' } else { throw new Error('Remote ou URL não fornecido e nenhum remote.origin.url configurado') } } await git.pull(opts); return { ok: true } }
45
+ if (action === 'clone') { const url = args?.url; const ref = args?.branch || 'main'; const onAuth = ({ url }) => { if (url.includes('github.com')) return { username: env.GITHUB_TOKEN, password: 'x-oauth-basic' }; if (env.GITEA_URL && url.includes(new URL(env.GITEA_URL).host)) return { username: env.GITEA_TOKEN, password: '' }; return {} } ; await git.clone({ fs, http, dir, url, ref }); return { ok: true } }
46
+ return { error: 'ação inválida' }
47
+ }
48
+ }
49
+
50
+ export function getSchema() {
51
+ return makeSchema({
52
+ name: 'git-local',
53
+ description: 'Operações locais Git independentes de git nativo',
54
+ actions: {
55
+ status: { description: 'Listar estado dos arquivos' },
56
+ init: { description: 'Inicializar repositório local' },
57
+ add: { description: 'Adicionar arquivos ao índice' },
58
+ commit: { description: 'Criar commit local' },
59
+ branch: { description: 'Gerenciar branches (list/create/switch)' },
60
+ tag: { description: 'Gerenciar tags (list/create)' },
61
+ push: { description: 'Enviar para remoto ou URL com fallback' },
62
+ pull: { description: 'Buscar alterações do remoto/URL' },
63
+ clone: { description: 'Clonar repo para projectPath' }
64
+ }
65
+ })
66
+ }
@@ -0,0 +1,19 @@
1
+ export class GitMetaTool {
2
+ constructor(env) { this.env = env }
3
+ async handle(action, projectPath, args) {
4
+ if (action === 'list') {
5
+ const names = ['git-local','git-remote','git-sync','git-repos','git-contents','git-commits','git-issues-prs','git-releases','git-actions','git-activity','git-search','git-gitdata','git-checks','git-deployments','git-misc','git-gists','git-packages','git-raw','git-admin','git-user','git-orgs']
6
+ const res = {}
7
+ for (const n of names) {
8
+ const mod = await import(`./${n}.js`)
9
+ if (mod.getSchema) res[n] = mod.getSchema()
10
+ }
11
+ return res
12
+ }
13
+ if (action === 'get') {
14
+ const mod = await import(`./${args?.name}.js`)
15
+ return mod.getSchema ? mod.getSchema() : null
16
+ }
17
+ return { error: 'ação inválida' }
18
+ }
19
+ }
@@ -0,0 +1,21 @@
1
+ import { Gitea } from '../providers/gitea.js'
2
+ import { Github } from '../providers/github.js'
3
+ import { makeSchema } from './schema.js'
4
+
5
+ export class GitMiscTool {
6
+ constructor(env) { this.gitea = new Gitea(env.GITEA_URL, env.GITEA_TOKEN); this.github = new Github(env.GITHUB_TOKEN) }
7
+ async handle(action, projectPath, args) {
8
+ if (action === 'version') return await this.gitea.request('GET', '/version')
9
+ if (action === 'markdown') { const text = args?.text || '# Hello'; const r = await Promise.allSettled([ this.gitea.request('POST', '/markdown', { text, mode: 'markdown' }), this.github.request('POST', '/markdown', { text }) ]); return r.map(x => x.status === 'fulfilled' ? x.value : { error: x.reason?.message }) }
10
+ if (action === 'markdown-raw') return await this.gitea.request('POST', '/markdown/raw', { body: args?.body || '# Hello' })
11
+ if (action === 'licenses') return await this.gitea.request('GET', '/licenses')
12
+ if (action === 'gitignore-templates') return await this.gitea.request('GET', '/gitignore/templates')
13
+ if (action === 'label-templates') return await this.gitea.request('GET', '/label/templates')
14
+ if (action === 'emojis') return await this.github.request('GET', '/emojis')
15
+ if (action === 'rate_limit') return await this.github.request('GET', '/rate_limit')
16
+ if (action === 'zen') return await this.github.request('GET', '/zen')
17
+ return { error: 'ação inválida' }
18
+ }
19
+ }
20
+
21
+ export function getSchema() { return makeSchema({ name: 'git-misc', description: 'Endpoints diversos (Gitea/GitHub)', actions: { version: { description: 'Versão do servidor Gitea' }, markdown: { description: 'Renderizar markdown' }, 'markdown-raw': { description: 'Renderizar markdown raw (Gitea)' }, licenses: { description: 'Licenças (Gitea)' }, 'gitignore-templates': { description: 'Templates gitignore (Gitea)' }, 'label-templates': { description: 'Templates de labels (Gitea)' }, emojis: { description: 'Emojis (GitHub)' }, rate_limit: { description: 'Limite de rate (GitHub)' }, zen: { description: 'Frase zen (GitHub)' } } }) }