@andrebuzeli/git-mcp 10.0.9 → 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 (137) 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/test_gitea_creation.d.ts +0 -1
  57. package/dist/scripts/test_gitea_creation.js +0 -116
  58. package/dist/scripts/verify_setup.d.ts +0 -1
  59. package/dist/scripts/verify_setup.js +0 -61
  60. package/dist/server.d.ts +0 -9
  61. package/dist/server.js +0 -73
  62. package/dist/tools/gitAnalytics.d.ts +0 -35
  63. package/dist/tools/gitAnalytics.js +0 -220
  64. package/dist/tools/gitArchive.d.ts +0 -119
  65. package/dist/tools/gitArchive.js +0 -150
  66. package/dist/tools/gitBackup.d.ts +0 -116
  67. package/dist/tools/gitBackup.js +0 -156
  68. package/dist/tools/gitBranches.d.ts +0 -54
  69. package/dist/tools/gitBranches.js +0 -282
  70. package/dist/tools/gitChangelog.d.ts +0 -37
  71. package/dist/tools/gitChangelog.js +0 -67
  72. package/dist/tools/gitConfig.d.ts +0 -97
  73. package/dist/tools/gitConfig.js +0 -125
  74. package/dist/tools/gitFiles.d.ts +0 -129
  75. package/dist/tools/gitFiles.js +0 -213
  76. package/dist/tools/gitFix.d.ts +0 -4
  77. package/dist/tools/gitFix.js +0 -159
  78. package/dist/tools/gitFix.tool.d.ts +0 -31
  79. package/dist/tools/gitFix.tool.js +0 -92
  80. package/dist/tools/gitHistory.d.ts +0 -41
  81. package/dist/tools/gitHistory.js +0 -349
  82. package/dist/tools/gitIgnore.d.ts +0 -214
  83. package/dist/tools/gitIgnore.js +0 -338
  84. package/dist/tools/gitIssues.d.ts +0 -80
  85. package/dist/tools/gitIssues.js +0 -363
  86. package/dist/tools/gitLog.d.ts +0 -30
  87. package/dist/tools/gitLog.js +0 -46
  88. package/dist/tools/gitMonitor.d.ts +0 -30
  89. package/dist/tools/gitMonitor.js +0 -284
  90. package/dist/tools/gitPackages.d.ts +0 -180
  91. package/dist/tools/gitPackages.js +0 -214
  92. package/dist/tools/gitPulls.d.ts +0 -66
  93. package/dist/tools/gitPulls.js +0 -347
  94. package/dist/tools/gitPush.d.ts +0 -40
  95. package/dist/tools/gitPush.js +0 -59
  96. package/dist/tools/gitRelease.d.ts +0 -49
  97. package/dist/tools/gitRelease.js +0 -359
  98. package/dist/tools/gitRemote.d.ts +0 -47
  99. package/dist/tools/gitRemote.js +0 -111
  100. package/dist/tools/gitReset.d.ts +0 -57
  101. package/dist/tools/gitReset.js +0 -79
  102. package/dist/tools/gitStash.d.ts +0 -61
  103. package/dist/tools/gitStash.js +0 -80
  104. package/dist/tools/gitSync.d.ts +0 -34
  105. package/dist/tools/gitSync.js +0 -182
  106. package/dist/tools/gitTags.d.ts +0 -45
  107. package/dist/tools/gitTags.js +0 -251
  108. package/dist/tools/gitUpdate.d.ts +0 -60
  109. package/dist/tools/gitUpdate.js +0 -474
  110. package/dist/tools/gitUpload.d.ts +0 -35
  111. package/dist/tools/gitUpload.js +0 -385
  112. package/dist/tools/gitWorkflow.d.ts +0 -117
  113. package/dist/tools/gitWorkflow.js +0 -472
  114. package/dist/types.d.ts +0 -20
  115. package/dist/types.js +0 -1
  116. package/dist/utils/agentHelpers.d.ts +0 -11
  117. package/dist/utils/agentHelpers.js +0 -41
  118. package/dist/utils/apiHelpers.d.ts +0 -29
  119. package/dist/utils/apiHelpers.js +0 -125
  120. package/dist/utils/cache.d.ts +0 -96
  121. package/dist/utils/cache.js +0 -208
  122. package/dist/utils/contextDetector.d.ts +0 -0
  123. package/dist/utils/contextDetector.js +0 -1
  124. package/dist/utils/errors.d.ts +0 -13
  125. package/dist/utils/errors.js +0 -17
  126. package/dist/utils/gitAdapter.d.ts +0 -224
  127. package/dist/utils/gitAdapter.js +0 -1162
  128. package/dist/utils/logger.d.ts +0 -45
  129. package/dist/utils/logger.js +0 -140
  130. package/dist/utils/rateLimiter.d.ts +0 -113
  131. package/dist/utils/rateLimiter.js +0 -257
  132. package/dist/utils/repoHelpers.d.ts +0 -44
  133. package/dist/utils/repoHelpers.js +0 -122
  134. package/dist/utils/safetyController.d.ts +0 -1
  135. package/dist/utils/safetyController.js +0 -12
  136. package/dist/utils/validation.d.ts +0 -115
  137. package/dist/utils/validation.js +0 -270
@@ -0,0 +1,26 @@
1
+ import { Gitea } from '../providers/gitea.js'
2
+ import { Github } from '../providers/github.js'
3
+ import { makeSchema } from './schema.js'
4
+
5
+ export class GitOrgsTool {
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 === 'create') return await this.gitea.request('POST', '/orgs', { username: args?.org_name, visibility: args?.visibility || 'public' })
9
+ if (action === 'list') { const r = await Promise.allSettled([ this.gitea.request('GET', '/orgs'), this.github.request('GET', '/user/orgs') ]); return r.map(x => x.status === 'fulfilled' ? x.value : { error: x.reason?.message }) }
10
+ if (action === 'get') { const r = await Promise.allSettled([ this.gitea.request('GET', `/orgs/${args?.org_name}`), this.github.request('GET', `/orgs/${args?.org_name}`) ]); return r.map(x => x.status === 'fulfilled' ? x.value : { error: x.reason?.message }) }
11
+ if (action === 'members') return await this.gitea.request('GET', `/orgs/${args?.org_name}/members`)
12
+ if (action === 'public_members') return await this.gitea.request('GET', `/orgs/${args?.org_name}/public_members`)
13
+ if (action === 'repos') return await this.gitea.request('GET', `/orgs/${args?.org_name}/repos`)
14
+ if (action === 'activities') return await this.gitea.request('GET', `/orgs/${args?.org_name}/activities/feeds`)
15
+ if (action === 'hooks') return await this.gitea.request('GET', `/orgs/${args?.org_name}/hooks`)
16
+ if (action === 'team-create') return await this.gitea.request('POST', `/orgs/${args?.org_name}/teams`, { name: args?.name || 'Team', permission: args?.permission || 'read' })
17
+ if (action === 'team-get') return await this.gitea.request('GET', `/api/v1/teams/${args?.team_id}`)
18
+ if (action === 'team-members') return await this.gitea.request('GET', `/teams/${args?.team_id}/members`)
19
+ if (action === 'team-repos') return await this.gitea.request('GET', `/teams/${args?.team_id}/repos`)
20
+ if (action === 'labels') return await this.gitea.request('GET', `/orgs/${args?.org_name}/labels`)
21
+ if (action === 'delete') return await this.gitea.request('DELETE', `/orgs/${args?.org_name}`)
22
+ return { error: 'ação inválida' }
23
+ }
24
+ }
25
+
26
+ export function getSchema() { return makeSchema({ name: 'git-orgs', description: 'Organizações e times (Gitea)', actions: { create: { description: 'Criar organização' }, list: { description: 'Listar organizações' }, get: { description: 'Obter organização' }, members: { description: 'Membros' }, public_members: { description: 'Membros públicos' }, repos: { description: 'Repositórios' }, activities: { description: 'Atividades' }, hooks: { description: 'Hooks' }, 'team-create': { description: 'Criar time' }, 'team-get': { description: 'Obter time' }, 'team-members': { description: 'Membros do time' }, 'team-repos': { description: 'Repos do time' }, labels: { description: 'Labels da org' }, delete: { description: 'Excluir organização' } } }) }
@@ -0,0 +1,12 @@
1
+ import { Gitea } from '../providers/gitea.js'
2
+ import { makeSchema } from './schema.js'
3
+
4
+ export class GitPackagesTool {
5
+ constructor(env) { this.gitea = new Gitea(env.GITEA_URL, env.GITEA_TOKEN) }
6
+ async handle(action, projectPath, args) {
7
+ if (action === 'list') { const me = await this.gitea.me(); return await this.gitea.request('GET', `/packages/${me.login}`) }
8
+ return { error: 'ação inválida' }
9
+ }
10
+ }
11
+
12
+ export function getSchema() { return makeSchema({ name: 'git-packages', description: 'Pacotes (Gitea)', actions: { list: { description: 'Listar pacotes do usuário' } } }) }
@@ -0,0 +1,14 @@
1
+ import { Gitea } from '../providers/gitea.js'
2
+ import { deriveProjectName } from '../utils/project.js'
3
+ import { makeSchema } from './schema.js'
4
+
5
+ export class GitRawTool {
6
+ constructor(env) { this.gitea = new Gitea(env.GITEA_URL, env.GITEA_TOKEN) }
7
+ async handle(action, projectPath, args) {
8
+ const me = await this.gitea.me(); const name = deriveProjectName(projectPath); const repo = `${me.login}/${name}`
9
+ if (action === 'get') return await this.gitea.request('GET', `/repos/${repo}/raw/${args?.path || 'test.txt'}`)
10
+ return { error: 'ação inválida' }
11
+ }
12
+ }
13
+
14
+ export function getSchema() { return makeSchema({ name: 'git-raw', description: 'Conteúdo bruto (Gitea)', actions: { get: { description: 'Obter arquivo bruto' } } }) }
@@ -0,0 +1,17 @@
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 GitReleasesTool {
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
+ if (action === 'create') { const tag = args?.tag || 'v1.0.0'; const title = args?.title || tag; const body = args?.body || ''; const r = await Promise.allSettled([ this.providers.gitea.createRelease(name, tag, title, body), this.providers.github.createRelease(name, tag, title, body) ]); return r.map(x => x.status === 'fulfilled' ? x.value : { error: x.reason?.message }) }
11
+ return { error: 'ação inválida' }
12
+ }
13
+ }
14
+
15
+ export function getSchema() {
16
+ return makeSchema({ name: 'git-releases', description: 'Gerenciar releases', actions: { create: { description: 'Criar release (tag/title/body)' } } })
17
+ }
@@ -0,0 +1,29 @@
1
+ import { Gitea } from '../providers/gitea.js'
2
+ import { Github } from '../providers/github.js'
3
+ import { deriveProjectName } from '../utils/project.js'
4
+ import fs from 'fs'
5
+ import git from 'isomorphic-git'
6
+ import { makeSchema } from './schema.js'
7
+
8
+ export class GitRemoteTool {
9
+ constructor(env) { this.env = env; this.providers = { gitea: new Gitea(env.GITEA_URL, env.GITEA_TOKEN), github: new Github(env.GITHUB_TOKEN) } }
10
+ async handle(action, projectPath, args) {
11
+ const name = deriveProjectName(projectPath)
12
+ if (action === 'createRepo') { const r = await Promise.allSettled([ this.providers.gitea.createRepo(name, false, true), this.providers.github.createRepo(name, false, true) ]); return r.map(x => x.status === 'fulfilled' ? x.value : { error: x.reason?.message }) }
13
+ if (action === 'deleteRepo') { const r = await Promise.allSettled([ this.providers.gitea.deleteRepo(name), this.providers.github.deleteRepo(name) ]); return r.map(x => x.status === 'fulfilled' ? x.value : { error: x.reason?.message }) }
14
+ if (action === 'ensureRemotes') { const urls = await Promise.all([ this.providers.gitea.repoHttpsUrl(name), this.providers.github.repoHttpsUrl(name) ]); try { await git.addRemote({ fs, dir: projectPath, remote: 'origin-gitea', url: urls[0] }) } catch {} ; try { await git.addRemote({ fs, dir: projectPath, remote: 'origin-github', url: urls[1] }) } catch {} ; return { gitea: urls[0], github: urls[1] } }
15
+ return { error: 'ação inválida' }
16
+ }
17
+ }
18
+
19
+ export function getSchema() {
20
+ return makeSchema({
21
+ name: 'git-remote',
22
+ description: 'Operações remotas simultâneas Gitea/GitHub (repos/remotes)',
23
+ actions: {
24
+ createRepo: { description: 'Criar repositório nos provedores' },
25
+ deleteRepo: { description: 'Excluir repositório nos provedores' },
26
+ ensureRemotes: { description: 'Garantir remotes origin-gitea/github' }
27
+ }
28
+ })
29
+ }
@@ -0,0 +1,60 @@
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 GitReposTool {
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 repoFullG = `${meG.login}/${name}`
12
+ const repoFullH = `${meH.login}/${name}`
13
+ 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 }) }
14
+
15
+ if (action === 'get') return await par(() => this.providers.gitea.request('GET', `/repos/${repoFullG}`), () => this.providers.github.request('GET', `/repos/${repoFullH}`))
16
+ if (action === 'patch') return await par(() => this.providers.gitea.request('PATCH', `/repos/${repoFullG}`, { description: args?.description || 'Updated' }), () => this.providers.github.request('PATCH', `/repos/${repoFullH}`, { description: args?.description || 'Updated' }))
17
+ if (action === 'languages') return await par(() => this.providers.gitea.request('GET', `/repos/${repoFullG}/languages`), () => this.providers.github.request('GET', `/repos/${repoFullH}/languages`))
18
+ if (action === 'tags') return await par(() => this.providers.gitea.request('GET', `/repos/${repoFullG}/tags`), () => this.providers.github.request('GET', `/repos/${repoFullH}/tags`))
19
+ if (action === 'topics-get') return await par(() => this.providers.gitea.request('GET', `/repos/${repoFullG}/topics`), () => this.providers.github.request('GET', `/repos/${repoFullH}/topics`))
20
+ if (action === 'topics-put') return await par(() => this.providers.gitea.request('PUT', `/repos/${repoFullG}/topics`, { topics: args?.topics || ['test'] }), () => this.providers.github.request('PUT', `/repos/${repoFullH}/topics`, { names: args?.topics || ['test'] }))
21
+ if (action === 'collaborators') return await par(() => this.providers.gitea.request('GET', `/repos/${repoFullG}/collaborators`), () => this.providers.github.request('GET', `/repos/${repoFullH}/collaborators`))
22
+ if (action === 'subscribers') return await par(() => this.providers.gitea.request('GET', `/repos/${repoFullG}/subscribers`), () => this.providers.github.request('GET', `/repos/${repoFullH}/subscribers`))
23
+ if (action === 'subscription-get') return await par(() => this.providers.gitea.request('GET', `/repos/${repoFullG}/subscription`), () => this.providers.github.request('GET', `/repos/${repoFullH}/subscription`))
24
+ if (action === 'subscription-put') return await par(() => this.providers.gitea.request('PUT', `/repos/${repoFullG}/subscription`, { subscribed: true }), () => this.providers.github.request('PUT', `/repos/${repoFullH}/subscription`, { subscribed: true }))
25
+ if (action === 'subscription-delete') return await par(() => this.providers.gitea.request('DELETE', `/repos/${repoFullG}/subscription`), () => this.providers.github.request('DELETE', `/repos/${repoFullH}/subscription`))
26
+ if (action === 'stargazers') return await par(() => this.providers.gitea.request('GET', `/repos/${repoFullG}/stargazers`), () => this.providers.github.request('GET', `/repos/${repoFullH}/stargazers`))
27
+ if (action === 'forks-get') return await par(() => this.providers.gitea.request('GET', `/repos/${repoFullG}/forks`), () => this.providers.github.request('GET', `/repos/${repoFullH}/forks`))
28
+ if (action === 'forks-post') return await par(() => this.providers.gitea.request('POST', `/repos/${repoFullG}/forks`), () => this.providers.github.request('POST', `/repos/${repoFullH}/forks`))
29
+ if (action === 'activities') return await par(() => this.providers.gitea.request('GET', `/repos/${repoFullG}/activities/feeds`), () => this.providers.github.request('GET', `/repos/${repoFullH}/events`))
30
+ if (action === 'hooks') return await par(() => this.providers.gitea.request('GET', `/repos/${repoFullG}/hooks`), () => this.providers.github.request('GET', `/repos/${repoFullH}/hooks`))
31
+ if (action === 'keys') return await par(() => this.providers.gitea.request('GET', `/repos/${repoFullG}/keys`), () => this.providers.github.request('GET', `/repos/${repoFullH}/keys`))
32
+ return { error: 'ação inválida' }
33
+ }
34
+ }
35
+
36
+ export function getSchema() {
37
+ return makeSchema({
38
+ name: 'git-repos',
39
+ description: 'Metadados e relações de repositórios',
40
+ actions: {
41
+ get: { description: 'Obter repositório' },
42
+ patch: { description: 'Atualizar descrição' },
43
+ languages: { description: 'Listar linguagens' },
44
+ tags: { description: 'Listar tags' },
45
+ 'topics-get': { description: 'Obter tópicos' },
46
+ 'topics-put': { description: 'Definir tópicos' },
47
+ collaborators: { description: 'Listar colaboradores' },
48
+ subscribers: { description: 'Listar assinantes' },
49
+ 'subscription-get': { description: 'Obter assinatura' },
50
+ 'subscription-put': { description: 'Assinar repo' },
51
+ 'subscription-delete': { description: 'Cancelar assinatura' },
52
+ stargazers: { description: 'Listar estrelas' },
53
+ 'forks-get': { description: 'Listar forks' },
54
+ 'forks-post': { description: 'Criar fork' },
55
+ activities: { description: 'Feed de atividades' },
56
+ hooks: { description: 'Hooks do repo' },
57
+ keys: { description: 'Chaves do repo' }
58
+ }
59
+ })
60
+ }
@@ -0,0 +1,18 @@
1
+ import { Gitea } from '../providers/gitea.js'
2
+ import { Github } from '../providers/github.js'
3
+ import { makeSchema } from './schema.js'
4
+
5
+ export class GitSearchTool {
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 === 'repos') { const q = args?.q || 'user:defunkt'; return await this.github.request('GET', `/search/repositories?q=${encodeURIComponent(q)}`) }
9
+ if (action === 'users') { const q = args?.q || 'tom'; return await this.github.request('GET', `/search/users?q=${encodeURIComponent(q)}`) }
10
+ if (action === 'issues') { const q = args?.q || 'type:issue+user:defunkt'; return await this.github.request('GET', `/search/issues?q=${encodeURIComponent(q)}`) }
11
+ if (action === 'repos-gitea') return await this.gitea.request('GET', '/repos/search')
12
+ return { error: 'ação inválida' }
13
+ }
14
+ }
15
+
16
+ export function getSchema() {
17
+ return makeSchema({ name: 'git-search', description: 'Busca em GitHub/Gitea', actions: { repos: { description: 'Buscar repositórios (GitHub)' }, users: { description: 'Buscar usuários (GitHub)' }, issues: { description: 'Buscar issues (GitHub)' }, 'repos-gitea': { description: 'Buscar repositórios (Gitea)' } } })
18
+ }
@@ -0,0 +1,40 @@
1
+ import { deriveProjectName } from '../utils/project.js'
2
+ import { GitLocalTool } from './git-local.js'
3
+ import { GitRemoteTool } from './git-remote.js'
4
+ import { listFilesRecursive, toProjectRelative, readBase64 } from '../utils/fs.js'
5
+ import { Gitea } from '../providers/gitea.js'
6
+ import { Github } from '../providers/github.js'
7
+ import { makeSchema } from './schema.js'
8
+
9
+ export class GitSyncTool {
10
+ constructor(env) { this.env = env; this.local = new GitLocalTool(); this.remote = new GitRemoteTool(env) }
11
+ async handle(action, projectPath, args) {
12
+ if (action === 'mirror') {
13
+ await this.remote.handle('createRepo', projectPath, args)
14
+ const rem = await this.remote.handle('ensureRemotes', projectPath, args)
15
+ try { await this.local.handle('push', projectPath, { remote: 'origin-gitea', branch: args?.branch || 'master' }, this.env) } catch {}
16
+ try { await this.local.handle('push', projectPath, { remote: 'origin-github', branch: args?.branch || 'master' }, this.env) } catch {}
17
+ // Fallback por API: subir todos arquivos via contents
18
+ const files = listFilesRecursive(projectPath)
19
+ const gitea = new Gitea(this.env.GITEA_URL, this.env.GITEA_TOKEN)
20
+ const github = new Github(this.env.GITHUB_TOKEN)
21
+ const name = deriveProjectName(projectPath)
22
+ for (const f of files) {
23
+ const rel = toProjectRelative(projectPath, f)
24
+ const content = readBase64(f)
25
+ try { await gitea.putContent(name, rel, content, 'mcp sync', args?.branch || 'master') } catch {}
26
+ try { await github.putContent(name, rel, content, 'mcp sync', args?.branch || 'master') } catch {}
27
+ }
28
+ return { ok: true }
29
+ }
30
+ return { error: 'ação inválida' }
31
+ }
32
+ }
33
+
34
+ export function getSchema() {
35
+ return makeSchema({
36
+ name: 'git-sync',
37
+ description: 'Espelhamento e sincronização completa entre provedores',
38
+ actions: { mirror: { description: 'Push e fallback via Contents API' } }
39
+ })
40
+ }
@@ -0,0 +1,26 @@
1
+ import { Gitea } from '../providers/gitea.js'
2
+ import { Github } from '../providers/github.js'
3
+ import { makeSchema } from './schema.js'
4
+
5
+ export class GitUserTool {
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 === 'me') { const r = await Promise.allSettled([ this.gitea.request('GET', '/user'), this.github.request('GET', '/user') ]); return r.map(x => x.status === 'fulfilled' ? x.value : { error: x.reason?.message }) }
9
+ if (action === 'users-search') return await this.gitea.request('GET', '/users/search')
10
+ if (action === 'user-get') return await this.gitea.request('GET', `/users/${args?.username}`)
11
+ if (action === 'heatmap') return await this.gitea.request('GET', `/users/${args?.username}/heatmap`)
12
+ if (action === 'followers') { const r = await Promise.allSettled([ this.gitea.request('GET', `/users/${args?.username}/followers`), this.github.request('GET', '/user/followers') ]); return r.map(x => x.status === 'fulfilled' ? x.value : { error: x.reason?.message }) }
13
+ if (action === 'following') { const r = await Promise.allSettled([ this.gitea.request('GET', `/users/${args?.username}/following`), this.github.request('GET', '/user/following') ]); return r.map(x => x.status === 'fulfilled' ? x.value : { error: x.reason?.message }) }
14
+ if (action === 'keys') { const r = await Promise.allSettled([ this.gitea.request('GET', `/users/${args?.username}/keys`), this.github.request('GET', '/user/keys') ]); return r.map(x => x.status === 'fulfilled' ? x.value : { error: x.reason?.message }) }
15
+ if (action === 'gpg_keys') { const r = await Promise.allSettled([ this.gitea.request('GET', `/users/${args?.username}/gpg_keys`), this.github.request('GET', '/user/gpg_keys') ]); return r.map(x => x.status === 'fulfilled' ? x.value : { error: x.reason?.message }) }
16
+ if (action === 'oauth2-apps') return await this.gitea.request('GET', '/user/applications/oauth2')
17
+ if (action === 'emails') { const r = await Promise.allSettled([ this.gitea.request('GET', '/user/emails'), this.github.request('GET', '/user/emails') ]); return r.map(x => x.status === 'fulfilled' ? x.value : { error: x.reason?.message }) }
18
+ if (action === 'starred') { const r = await Promise.allSettled([ this.gitea.request('GET', '/user/starred'), this.github.request('GET', '/user/starred') ]); return r.map(x => x.status === 'fulfilled' ? x.value : { error: x.reason?.message }) }
19
+ if (action === 'subscriptions') { const r = await Promise.allSettled([ this.gitea.request('GET', '/user/subscriptions'), this.github.request('GET', '/user/subscriptions') ]); return r.map(x => x.status === 'fulfilled' ? x.value : { error: x.reason?.message }) }
20
+ if (action === 'stopwatches') return await this.gitea.request('GET', '/user/stopwatches')
21
+ if (action === 'settings') return await this.gitea.request('GET', '/user/settings')
22
+ return { error: 'ação inválida' }
23
+ }
24
+ }
25
+
26
+ export function getSchema() { return makeSchema({ name: 'git-user', description: 'Perfil e dados de usuário (Gitea/GitHub)', actions: { me: { description: 'Dados do usuário autenticado' }, 'users-search': { description: 'Buscar usuários (Gitea)' }, 'user-get': { description: 'Obter usuário (Gitea)' }, heatmap: { description: 'Heatmap (Gitea)' }, followers: { description: 'Seguidores' }, following: { description: 'Seguindo' }, keys: { description: 'Chaves SSH' }, gpg_keys: { description: 'Chaves GPG' }, 'oauth2-apps': { description: 'Apps OAuth2 (Gitea)' }, emails: { description: 'Emails' }, starred: { description: 'Repos estrelados' }, subscriptions: { description: 'Assinaturas' }, stopwatches: { description: 'Cronômetros (Gitea)' }, settings: { description: 'Configurações (Gitea)' } } }) }
@@ -0,0 +1,3 @@
1
+ export function makeSchema({ name, description, params = ["projectPath", "action", "args"], actions }) {
2
+ return { name, description, params, actions }
3
+ }
@@ -0,0 +1,29 @@
1
+ import fs from 'fs'
2
+ import path from 'path'
3
+
4
+ export function listFilesRecursive(dir) {
5
+ const out = []
6
+ function walk(d) {
7
+ const entries = fs.readdirSync(d, { withFileTypes: true })
8
+ for (const e of entries) {
9
+ const p = path.join(d, e.name)
10
+ if (e.isDirectory()) {
11
+ if (e.name === '.git') continue
12
+ walk(p)
13
+ } else {
14
+ out.push(p)
15
+ }
16
+ }
17
+ }
18
+ walk(dir)
19
+ return out
20
+ }
21
+
22
+ export function toProjectRelative(dir, filePath) {
23
+ return path.relative(dir, filePath).split(path.sep).join('/')
24
+ }
25
+
26
+ export function readBase64(filePath) {
27
+ const buf = fs.readFileSync(filePath)
28
+ return buf.toString('base64')
29
+ }
@@ -0,0 +1,7 @@
1
+ import path from 'path'
2
+
3
+ export function deriveProjectName(projectPath) {
4
+ const base = path.basename(projectPath).replace(/\s+/g, '_')
5
+ const s = base.replace(/[^A-Za-z0-9_.-]/g, '_').replace(/_+/g, '_').replace(/^\.+/, '')
6
+ return s
7
+ }
@@ -0,0 +1,26 @@
1
+ import { callOnce } from '../src/server.js'
2
+ import fs from 'fs'
3
+ const projectPath = process.env.TEST_PROJECT || 'Z:/MCP/GIT MCP/temp_git_mcp_test'
4
+
5
+ async function main() {
6
+ if (!fs.existsSync(projectPath)) fs.mkdirSync(projectPath, { recursive: true })
7
+ fs.writeFileSync(projectPath + '/err.txt', 'X')
8
+ const wrap = async (name, fn) => { try { const r = await fn(); console.log(name + ': OK' + (r?.fallback ? ' (fallback)' : '')) } catch (e) { console.log(name + ': ERR ' + e?.message) } }
9
+
10
+ // Missing remote -> ensureRemotes then push
11
+ await wrap('ensureRemotes', () => callOnce({ tool: 'git-remote', action: 'ensureRemotes', projectPath }))
12
+ await wrap('push-fallback', () => callOnce({ tool: 'git-local', action: 'push', projectPath, args: { branch: 'master' } }))
13
+
14
+ // Issue labels with nonexistent label -> autocreate and apply
15
+ await wrap('labels-create', () => callOnce({ tool: 'git-issues-prs', action: 'labels-create', projectPath, args: { name: 'bug', color: 'ff0000' } }))
16
+ const issue = await callOnce({ tool: 'git-issues-prs', action: 'issue-create', projectPath, args: { title: 'Err Issue', body: 'B' } })
17
+ const num = Array.isArray(issue) ? (issue[1]?.number) : issue?.number
18
+ await wrap('issue-labels-autocorrect', () => callOnce({ tool: 'git-issues-prs', action: 'issue-labels', projectPath, args: { number: num, labels: ['bug'] } }))
19
+
20
+ // PR head inexistente -> autocorreção criando branch/arquivo e reabrindo
21
+ await wrap('pr-create-autocorrect', () => callOnce({ tool: 'git-issues-prs', action: 'pr-create', projectPath, args: { title: 'PR Err', head: 'feature-branch', base: 'main' } }))
22
+
23
+ console.log('ERROR TESTS DONE')
24
+ }
25
+
26
+ main().catch(e => { console.error(e); process.exit(1) })
@@ -0,0 +1,98 @@
1
+ import { callOnce } from '../src/server.js'
2
+ import fs from 'fs'
3
+
4
+ function log(name, res) { const ok = typeof res === 'object' && res !== null && res.ok === false ? false : true; console.log(`${name}: ${ok ? 'OK' : 'ERR'}`) }
5
+
6
+ async function main() {
7
+ const projectPath = process.env.TEST_PROJECT || 'Z:/MCP/GIT MCP/temp_git_mcp_test_full'
8
+ if (!fs.existsSync(projectPath)) fs.mkdirSync(projectPath, { recursive: true })
9
+ fs.writeFileSync(projectPath + '/README.md', '# Full Suite')
10
+ fs.writeFileSync(projectPath + '/file.txt', 'content')
11
+
12
+ await callOnce({ tool: 'git-local', action: 'init', projectPath })
13
+ await callOnce({ tool: 'git-local', action: 'add', projectPath, args: { patterns: ['README.md', 'file.txt'] } })
14
+ await callOnce({ tool: 'git-local', action: 'commit', projectPath, args: { message: 'init' } })
15
+
16
+ log('git-remote.createRepo', await callOnce({ tool: 'git-remote', action: 'createRepo', projectPath }))
17
+ const rem = await callOnce({ tool: 'git-remote', action: 'ensureRemotes', projectPath })
18
+ log('git-remote.ensureRemotes', rem)
19
+
20
+ // Full mirror (push then contents fallback)
21
+ log('git-sync.mirror', await callOnce({ tool: 'git-sync', action: 'mirror', projectPath, args: { branch: 'master' } }))
22
+
23
+ // Repos metadata
24
+ log('git-repos.get', await callOnce({ tool: 'git-repos', action: 'get', projectPath }))
25
+ log('git-repos.patch', await callOnce({ tool: 'git-repos', action: 'patch', projectPath, args: { description: 'Updated by full suite' } }))
26
+ log('git-repos.languages', await callOnce({ tool: 'git-repos', action: 'languages', projectPath }))
27
+ log('git-repos.tags', await callOnce({ tool: 'git-repos', action: 'tags', projectPath }))
28
+ log('git-repos.topics-put', await callOnce({ tool: 'git-repos', action: 'topics-put', projectPath, args: { topics: ['mcp','git'] } }))
29
+ log('git-repos.subscription-get', await callOnce({ tool: 'git-repos', action: 'subscription-get', projectPath }))
30
+ log('git-repos.subscription-put', await callOnce({ tool: 'git-repos', action: 'subscription-put', projectPath }))
31
+ log('git-repos.subscription-delete', await callOnce({ tool: 'git-repos', action: 'subscription-delete', projectPath }))
32
+
33
+ // Contents
34
+ log('git-contents.create', await callOnce({ tool: 'git-contents', action: 'create', projectPath, args: { path: 'api.txt', content: Buffer.from('hello').toString('base64'), message: 'add api', branch: 'master' } }))
35
+ log('git-contents.get', await callOnce({ tool: 'git-contents', action: 'get', projectPath, args: { path: 'api.txt' } }))
36
+
37
+ // Commits & branches
38
+ log('git-commits.branches', await callOnce({ tool: 'git-commits', action: 'branches', projectPath }))
39
+ log('git-commits.create-branch', await callOnce({ tool: 'git-commits', action: 'create-branch', projectPath, args: { name: 'dev', from: 'master' } }))
40
+ log('git-commits.branch-protections', await callOnce({ tool: 'git-commits', action: 'branch-protections', projectPath }))
41
+ const refs = await callOnce({ tool: 'git-commits', action: 'refs', projectPath })
42
+ log('git-commits.refs', refs)
43
+ let sha
44
+ try { const ghRefs = Array.isArray(refs) ? refs[1] : refs; if (Array.isArray(ghRefs)) { const head = ghRefs.find(r => r?.ref?.endsWith('master') || r?.ref?.endsWith('main')); sha = head?.object?.sha } } catch {}
45
+
46
+ // Issues & PRs & labels & milestones
47
+ log('git-issues-prs.labels-create', await callOnce({ tool: 'git-issues-prs', action: 'labels-create', projectPath, args: { name: 'bug', color: 'ff0000' } }))
48
+ const issue = await callOnce({ tool: 'git-issues-prs', action: 'issue-create', projectPath, args: { title: 'Issue Full', body: 'Body' } })
49
+ log('git-issues-prs.issue-create', issue)
50
+ const issueNum = Array.isArray(issue) ? (issue[1]?.number || issue[0]?.number) : issue?.number
51
+ if (issueNum) {
52
+ log('git-issues-prs.issue-comment', await callOnce({ tool: 'git-issues-prs', action: 'issue-comment', projectPath, args: { number: issueNum, comment: 'Comment' } }))
53
+ log('git-issues-prs.issue-labels', await callOnce({ tool: 'git-issues-prs', action: 'issue-labels', projectPath, args: { number: issueNum, labels: ['bug'] } }))
54
+ }
55
+ log('git-issues-prs.milestones-create', await callOnce({ tool: 'git-issues-prs', action: 'milestones-create', projectPath, args: { title: 'v1.0' } }))
56
+ log('git-issues-prs.pr-create', await callOnce({ tool: 'git-issues-prs', action: 'pr-create', projectPath, args: { title: 'PR Full', head: 'dev', base: 'master' } }))
57
+
58
+ // Releases
59
+ log('git-releases.create', await callOnce({ tool: 'git-releases', action: 'create', projectPath, args: { tag: 'v1.0.0', title: 'v1.0.0', body: 'Desc' } }))
60
+
61
+ // Actions (GitHub)
62
+ log('git-actions.workflows', await callOnce({ tool: 'git-actions', action: 'workflows', projectPath }))
63
+ log('git-actions.runs', await callOnce({ tool: 'git-actions', action: 'runs', projectPath }))
64
+
65
+ // Activity
66
+ log('git-activity.notifications-get', await callOnce({ tool: 'git-activity', action: 'notifications-get', projectPath }))
67
+ log('git-activity.events', await callOnce({ tool: 'git-activity', action: 'events', projectPath }))
68
+
69
+ // Search
70
+ log('git-search.repos', await callOnce({ tool: 'git-search', action: 'repos', projectPath }))
71
+ log('git-search.users', await callOnce({ tool: 'git-search', action: 'users', projectPath }))
72
+
73
+ // Git Data (GitHub)
74
+ if (sha) {
75
+ log('git-checks.status', await callOnce({ tool: 'git-checks', action: 'status', projectPath, args: { sha, state: 'success', context: 'full-suite' } }))
76
+ log('git-deployments.create', await callOnce({ tool: 'git-deployments', action: 'create', projectPath }))
77
+ }
78
+
79
+ // Misc
80
+ log('git-misc.version', await callOnce({ tool: 'git-misc', action: 'version', projectPath }))
81
+ log('git-misc.markdown', await callOnce({ tool: 'git-misc', action: 'markdown', projectPath, args: { text: '# Hello' } }))
82
+
83
+ // Gists (GitHub)
84
+ const gist = await callOnce({ tool: 'git-gists', action: 'create', projectPath, args: { description: 'Full', public: false, files: { 't.txt': { content: 'x' } } } })
85
+ log('git-gists.create', gist)
86
+ const gid = gist?.id || gist?.[0]?.id || gist?.[1]?.id
87
+ if (gid) {
88
+ log('git-gists.star', await callOnce({ tool: 'git-gists', action: 'star', projectPath, args: { id: gid } }))
89
+ log('git-gists.delete', await callOnce({ tool: 'git-gists', action: 'delete', projectPath, args: { id: gid } }))
90
+ }
91
+
92
+ // Packages (Gitea)
93
+ log('git-packages.list', await callOnce({ tool: 'git-packages', action: 'list', projectPath }))
94
+
95
+ console.log('FULL SUITE DONE')
96
+ }
97
+
98
+ main().catch(e => { console.error(e); process.exit(1) })
package/tests/run.js ADDED
@@ -0,0 +1,50 @@
1
+ import { callOnce } from '../src/server.js'
2
+ import fs from 'fs'
3
+ const projectPath = process.env.TEST_PROJECT || 'Z:/MCP/GIT MCP/temp_git_mcp_test'
4
+
5
+ async function main() {
6
+ if (!fs.existsSync(projectPath)) fs.mkdirSync(projectPath, { recursive: true })
7
+ fs.writeFileSync(projectPath + '/README.md', 'Test')
8
+ const log = (name, res) => { console.log(name + ': ' + (res && res.ok === false ? 'ERR' : 'OK')) }
9
+
10
+ await callOnce({ tool: 'git-local', action: 'init', projectPath })
11
+ await callOnce({ tool: 'git-local', action: 'add', projectPath, args: { patterns: ['README.md'] } })
12
+ await callOnce({ tool: 'git-local', action: 'commit', projectPath, args: { message: 'init' } })
13
+
14
+ log('git-remote.createRepo', await callOnce({ tool: 'git-remote', action: 'createRepo', projectPath }))
15
+ const remotes = await callOnce({ tool: 'git-remote', action: 'ensureRemotes', projectPath })
16
+ log('git-remote.ensureRemotes', remotes)
17
+ await callOnce({ tool: 'git-local', action: 'addRemote', projectPath, args: { name: 'origin-gitea', url: remotes.gitea } })
18
+ await callOnce({ tool: 'git-local', action: 'addRemote', projectPath, args: { name: 'origin-github', url: remotes.github } })
19
+
20
+ log('git-local.push(github)', await callOnce({ tool: 'git-local', action: 'push', projectPath, args: { url: remotes.github, branch: 'master' } }))
21
+
22
+ const calls = [
23
+ { tool: 'git-repos', action: 'get' },
24
+ { tool: 'git-repos', action: 'languages' },
25
+ { tool: 'git-repos', action: 'tags' },
26
+ { tool: 'git-repos', action: 'topics-put', args: { topics: ['test'] } },
27
+ { tool: 'git-contents', action: 'create', args: { path: 'test.txt', content: Buffer.from('Hello').toString('base64'), message: 'Add file', branch: 'master' } },
28
+ { tool: 'git-contents', action: 'get', args: { path: 'test.txt' } },
29
+ { tool: 'git-commits', action: 'branches' },
30
+ { tool: 'git-commits', action: 'create-branch', args: { name: 'dev', from: 'main' } },
31
+ { tool: 'git-issues-prs', action: 'labels-create', args: { name: 'bug', color: 'ff0000' } },
32
+ { tool: 'git-issues-prs', action: 'issue-create', args: { title: 'Test Issue', body: 'Body' } },
33
+ { tool: 'git-releases', action: 'create', args: { tag: 'v1.0.0', title: 'v1.0.0', body: 'Release' } },
34
+ { tool: 'git-actions', action: 'workflows' },
35
+ { tool: 'git-activity', action: 'notifications-get' },
36
+ { tool: 'git-search', action: 'repos-gitea' },
37
+ { tool: 'git-misc', action: 'version' }
38
+ ]
39
+
40
+ for (const c of calls) {
41
+ const res = await callOnce({ tool: c.tool, action: c.action, projectPath, args: c.args })
42
+ log(`${c.tool}.${c.action}`, res)
43
+ }
44
+
45
+ // Mirror fallback via contents
46
+ await callOnce({ tool: 'git-sync', action: 'mirror', projectPath, args: { branch: 'master' } })
47
+ console.log('DONE')
48
+ }
49
+
50
+ main().catch(e => { console.error(e); process.exit(1) })
package/LICENSE DELETED
@@ -1,21 +0,0 @@
1
- MIT License
2
-
3
- Copyright (c) 2024 Andre Buzeli
4
-
5
- Permission is hereby granted, free of charge, to any person obtaining a copy
6
- of this software and associated documentation files (the "Software"), to deal
7
- in the Software without restriction, including without limitation the rights
8
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
- copies of the Software, and to permit persons to whom the Software is
10
- furnished to do so, subject to the following conditions:
11
-
12
- The above copyright notice and this permission notice shall be included in all
13
- copies or substantial portions of the Software.
14
-
15
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
- SOFTWARE.
package/dist/index.d.ts DELETED
@@ -1,2 +0,0 @@
1
- #!/usr/bin/env node
2
- export {};