@andrebuzeli/git-mcp 11.0.5 → 12.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 (55) hide show
  1. package/README.md +39 -47
  2. package/package.json +24 -19
  3. package/src/index.js +84 -0
  4. package/src/providers/providerManager.js +107 -0
  5. package/src/tools/git-branches.js +63 -0
  6. package/src/tools/git-config.js +53 -0
  7. package/src/tools/git-files.js +41 -0
  8. package/src/tools/git-history.js +36 -0
  9. package/src/tools/git-ignore.js +48 -0
  10. package/src/tools/git-issues.js +58 -12
  11. package/src/tools/git-pulls.js +61 -0
  12. package/src/tools/git-remote.js +182 -29
  13. package/src/tools/git-reset.js +35 -0
  14. package/src/tools/git-stash.js +57 -0
  15. package/src/tools/git-sync.js +44 -40
  16. package/src/tools/git-tags.js +58 -0
  17. package/src/tools/git-workflow.js +85 -0
  18. package/src/utils/errors.js +22 -0
  19. package/src/utils/gitAdapter.js +116 -0
  20. package/src/utils/providerExec.js +13 -0
  21. package/src/utils/repoHelpers.js +25 -0
  22. package/src/utils/retry.js +12 -0
  23. package/bin/git-mcp.js +0 -21
  24. package/docs/TOOLS.md +0 -110
  25. package/mcp.json.template +0 -12
  26. package/src/local/git.js +0 -14
  27. package/src/providers/gitea.js +0 -13
  28. package/src/providers/github.js +0 -13
  29. package/src/server.js +0 -130
  30. package/src/tools/git-actions.js +0 -19
  31. package/src/tools/git-activity.js +0 -28
  32. package/src/tools/git-admin.js +0 -20
  33. package/src/tools/git-checks.js +0 -14
  34. package/src/tools/git-commits.js +0 -34
  35. package/src/tools/git-contents.js +0 -30
  36. package/src/tools/git-deployments.js +0 -21
  37. package/src/tools/git-gists.js +0 -15
  38. package/src/tools/git-gitdata.js +0 -19
  39. package/src/tools/git-issues-prs.js +0 -44
  40. package/src/tools/git-local.js +0 -66
  41. package/src/tools/git-meta.js +0 -19
  42. package/src/tools/git-misc.js +0 -21
  43. package/src/tools/git-orgs.js +0 -26
  44. package/src/tools/git-packages.js +0 -12
  45. package/src/tools/git-raw.js +0 -14
  46. package/src/tools/git-releases.js +0 -17
  47. package/src/tools/git-repos.js +0 -60
  48. package/src/tools/git-search.js +0 -18
  49. package/src/tools/git-user.js +0 -26
  50. package/src/tools/schema.js +0 -3
  51. package/src/utils/fs.js +0 -29
  52. package/src/utils/project.js +0 -7
  53. package/tests/errors.js +0 -26
  54. package/tests/full_suite.js +0 -98
  55. package/tests/run.js +0 -50
@@ -1,30 +0,0 @@
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
- }
@@ -1,21 +0,0 @@
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' } } }) }
@@ -1,15 +0,0 @@
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' } } }) }
@@ -1,19 +0,0 @@
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
- }
@@ -1,44 +0,0 @@
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
- }
@@ -1,66 +0,0 @@
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
- }
@@ -1,19 +0,0 @@
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
- }
@@ -1,21 +0,0 @@
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)' } } }) }
@@ -1,26 +0,0 @@
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' } } }) }
@@ -1,12 +0,0 @@
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' } } }) }
@@ -1,14 +0,0 @@
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' } } }) }
@@ -1,17 +0,0 @@
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
- }
@@ -1,60 +0,0 @@
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
- }
@@ -1,18 +0,0 @@
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
- }
@@ -1,26 +0,0 @@
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)' } } }) }
@@ -1,3 +0,0 @@
1
- export function makeSchema({ name, description, params = ["projectPath", "action", "args"], actions }) {
2
- return { name, description, params, actions }
3
- }
package/src/utils/fs.js DELETED
@@ -1,29 +0,0 @@
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
- }
@@ -1,7 +0,0 @@
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
- }
package/tests/errors.js DELETED
@@ -1,26 +0,0 @@
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) })