mirror of
				https://gitea.com/gitea/gitea-mcp.git
				synced 2025-11-04 04:11:50 +00:00 
			
		
		
		
	Compare commits
	
		
			62 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					de311344cd | ||
| 
						 | 
					d7addd56c4 | ||
| 
						 | 
					dc3e120e97 | ||
| 
						 | 
					f33b04a3df | ||
| 
						 | 
					ba07925969 | ||
| 
						 | 
					5c2ff6dcb2 | ||
| 
						 | 
					feaedaf604 | ||
| 
						 | 
					a601d6b698 | ||
| 
						 | 
					62cb6e7830 | ||
| 
						 | 
					9fff996294 | ||
| 
						 | 
					4c3f5149d8 | ||
| 
						 | 
					eb6b5a8f92 | ||
| 
						 | 
					1d9bdb5b44 | ||
| 
						 | 
					093cddbcb6 | ||
| 
						 | 
					5dbfe21127 | ||
| 
						 | 
					b85a523983 | ||
| 
						 | 
					da08718e24 | ||
| 
						 | 
					44ea8969f4 | ||
| 
						 | 
					94aa8dc572 | ||
| 
						 | 
					05194ffc1c | ||
| 
						 | 
					5c329129f8 | ||
| 
						 | 
					52ccf92761 | ||
| 
						 | 
					061ea86b0b | ||
| 
						 | 
					f14b60fe56 | ||
| 
						 | 
					94782a85b6 | ||
| 
						 | 
					e94dd26b30 | ||
| 
						 | 
					da49bdeb96 | ||
| 
						 | 
					3f61299f72 | ||
| 
						 | 
					5308fbfb2b | ||
| 
						 | 
					a7061f9b64 | ||
| 
						 | 
					f25cc0de8c | ||
| 
						 | 
					417ef26da0 | ||
| 
						 | 
					34ca5d45db | ||
| 
						 | 
					796fd4682d | ||
| 
						 | 
					95c036bf3a | ||
| 
						 | 
					70b9ac5b80 | ||
| 
						 | 
					59e699aac7 | ||
| 
						 | 
					26c50d53bd | ||
| 
						 | 
					7bfc596a58 | ||
| 
						 | 
					966d617670 | ||
| 
						 | 
					af27b685d4 | ||
| 
						 | 
					fac6e1d8d1 | ||
| 
						 | 
					f656c92cda | ||
| 
						 | 
					af0975d93f | ||
| 
						 | 
					001383142f | ||
| 
						 | 
					b35919989f | ||
| 
						 | 
					d0225c4c24 | ||
| 
						 | 
					6993bb2b5d | ||
| 
						 | 
					f1b4a208a7 | ||
| 
						 | 
					d76f02a234 | ||
| 
						 | 
					b2bde61882 | ||
| 
						 | 
					7cfa1fa218 | ||
| 
						 | 
					1fecc1df30 | ||
| 
						 | 
					8dc9ed9299 | ||
| 
						 | 
					1965c9830b | ||
| 
						 | 
					f377f06478 | ||
| 
						 | 
					02fd91da86 | ||
| 
						 | 
					55f32ef4f5 | ||
| 
						 | 
					c9cada1a8d | ||
| 
						 | 
					a784029828 | ||
| 
						 | 
					f27c4c622d | ||
| 
						 | 
					df47a0c9eb | 
							
								
								
									
										52
									
								
								.air.toml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										52
									
								
								.air.toml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,52 @@
 | 
				
			|||||||
 | 
					root = "."
 | 
				
			||||||
 | 
					testdata_dir = "testdata"
 | 
				
			||||||
 | 
					tmp_dir = "tmp"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[build]
 | 
				
			||||||
 | 
					  args_bin = ["-t", "http"]
 | 
				
			||||||
 | 
					  bin = "./gitea-mcp"
 | 
				
			||||||
 | 
					  cmd = "make build"
 | 
				
			||||||
 | 
					  delay = 1000
 | 
				
			||||||
 | 
					  exclude_dir = ["assets", "tmp", "vendor", "testdata"]
 | 
				
			||||||
 | 
					  exclude_file = []
 | 
				
			||||||
 | 
					  exclude_regex = ["_test.go"]
 | 
				
			||||||
 | 
					  exclude_unchanged = false
 | 
				
			||||||
 | 
					  follow_symlink = false
 | 
				
			||||||
 | 
					  full_bin = ""
 | 
				
			||||||
 | 
					  include_dir = []
 | 
				
			||||||
 | 
					  include_ext = ["go", "tpl", "tmpl", "html"]
 | 
				
			||||||
 | 
					  include_file = []
 | 
				
			||||||
 | 
					  kill_delay = "0s"
 | 
				
			||||||
 | 
					  log = "build-errors.log"
 | 
				
			||||||
 | 
					  poll = false
 | 
				
			||||||
 | 
					  poll_interval = 0
 | 
				
			||||||
 | 
					  post_cmd = []
 | 
				
			||||||
 | 
					  pre_cmd = []
 | 
				
			||||||
 | 
					  rerun = false
 | 
				
			||||||
 | 
					  rerun_delay = 500
 | 
				
			||||||
 | 
					  send_interrupt = false
 | 
				
			||||||
 | 
					  stop_on_error = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[color]
 | 
				
			||||||
 | 
					  app = ""
 | 
				
			||||||
 | 
					  build = "yellow"
 | 
				
			||||||
 | 
					  main = "magenta"
 | 
				
			||||||
 | 
					  runner = "green"
 | 
				
			||||||
 | 
					  watcher = "cyan"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[log]
 | 
				
			||||||
 | 
					  main_only = false
 | 
				
			||||||
 | 
					  silent = false
 | 
				
			||||||
 | 
					  time = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[misc]
 | 
				
			||||||
 | 
					  clean_on_exit = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[proxy]
 | 
				
			||||||
 | 
					  app_port = 0
 | 
				
			||||||
 | 
					  enabled = false
 | 
				
			||||||
 | 
					  proxy_port = 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[screen]
 | 
				
			||||||
 | 
					  clear_on_rebuild = false
 | 
				
			||||||
 | 
					  keep_scroll = true
 | 
				
			||||||
@@ -1,8 +1,7 @@
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "name": "Gitea MCP DevContainer",
 | 
					  "name": "Gitea MCP DevContainer",
 | 
				
			||||||
  "image": "mcr.microsoft.com/devcontainers/go:1.24-bookworm",
 | 
					  "image": "mcr.microsoft.com/devcontainers/go:1.24-bookworm",
 | 
				
			||||||
    "features": {
 | 
					  "features": {},
 | 
				
			||||||
    },
 | 
					 | 
				
			||||||
  "customizations": {
 | 
					  "customizations": {
 | 
				
			||||||
    "vscode": {
 | 
					    "vscode": {
 | 
				
			||||||
      "settings": {},
 | 
					      "settings": {},
 | 
				
			||||||
@@ -12,7 +11,8 @@
 | 
				
			|||||||
        "golang.go",
 | 
					        "golang.go",
 | 
				
			||||||
        "stylelint.vscode-stylelint",
 | 
					        "stylelint.vscode-stylelint",
 | 
				
			||||||
        "DavidAnson.vscode-markdownlint",
 | 
					        "DavidAnson.vscode-markdownlint",
 | 
				
			||||||
          "github.copilot"
 | 
					        "github.copilot",
 | 
				
			||||||
 | 
					        "eamodio.gitlens"
 | 
				
			||||||
      ]
 | 
					      ]
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										61
									
								
								.dockerignore
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										61
									
								
								.dockerignore
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,61 @@
 | 
				
			|||||||
 | 
					# Git
 | 
				
			||||||
 | 
					.git
 | 
				
			||||||
 | 
					.gitignore
 | 
				
			||||||
 | 
					.github/
 | 
				
			||||||
 | 
					.gitea/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Docker
 | 
				
			||||||
 | 
					Dockerfile
 | 
				
			||||||
 | 
					.dockerignore
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Build artifacts
 | 
				
			||||||
 | 
					bin/
 | 
				
			||||||
 | 
					dist/
 | 
				
			||||||
 | 
					build/
 | 
				
			||||||
 | 
					*.exe
 | 
				
			||||||
 | 
					*.exe~
 | 
				
			||||||
 | 
					*.dll
 | 
				
			||||||
 | 
					*.so
 | 
				
			||||||
 | 
					*.dylib
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Go specific
 | 
				
			||||||
 | 
					vendor/
 | 
				
			||||||
 | 
					go.work
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Testing
 | 
				
			||||||
 | 
					*_test.go
 | 
				
			||||||
 | 
					**/test/
 | 
				
			||||||
 | 
					**/tests/
 | 
				
			||||||
 | 
					coverage.out
 | 
				
			||||||
 | 
					coverage.html
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# IDE and editor files
 | 
				
			||||||
 | 
					.idea/
 | 
				
			||||||
 | 
					.vscode/
 | 
				
			||||||
 | 
					*.swp
 | 
				
			||||||
 | 
					*.swo
 | 
				
			||||||
 | 
					*~
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# OS specific
 | 
				
			||||||
 | 
					.DS_Store
 | 
				
			||||||
 | 
					Thumbs.db
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Temporary files
 | 
				
			||||||
 | 
					tmp/
 | 
				
			||||||
 | 
					temp/
 | 
				
			||||||
 | 
					*.tmp
 | 
				
			||||||
 | 
					*.log
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Documentation
 | 
				
			||||||
 | 
					docs/
 | 
				
			||||||
 | 
					*.md
 | 
				
			||||||
 | 
					LICENSE
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Development tools
 | 
				
			||||||
 | 
					.air.toml
 | 
				
			||||||
 | 
					.golangci.yml
 | 
				
			||||||
 | 
					.goreleaser.yml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Debug files
 | 
				
			||||||
 | 
					debug
 | 
				
			||||||
 | 
					__debug_bin
 | 
				
			||||||
@@ -34,7 +34,7 @@ jobs:
 | 
				
			|||||||
        id: meta
 | 
					        id: meta
 | 
				
			||||||
        run: |
 | 
					        run: |
 | 
				
			||||||
          echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}') >> $GITHUB_OUTPUT
 | 
					          echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}') >> $GITHUB_OUTPUT
 | 
				
			||||||
          echo REPO_VERSION=$(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//') >> $GITHUB_OUTPUT
 | 
					          echo REPO_VERSION=$(git describe --tags --always | sed 's/-/+/' | sed 's/^v//') >> $GITHUB_OUTPUT
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      - name: Build and push
 | 
					      - name: Build and push
 | 
				
			||||||
        uses: docker/build-push-action@v5
 | 
					        uses: docker/build-push-action@v5
 | 
				
			||||||
@@ -3,39 +3,31 @@ name: release
 | 
				
			|||||||
on:
 | 
					on:
 | 
				
			||||||
  push:
 | 
					  push:
 | 
				
			||||||
    tags:
 | 
					    tags:
 | 
				
			||||||
      - '*'
 | 
					      - "*"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
jobs:
 | 
					jobs:
 | 
				
			||||||
  release:
 | 
					  goreleaser:
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    steps:
 | 
					    steps:
 | 
				
			||||||
      - uses: actions/checkout@v4
 | 
					      - name: Checkout
 | 
				
			||||||
 | 
					        uses: actions/checkout@v4
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          fetch-depth: 0
 | 
					          fetch-depth: 0
 | 
				
			||||||
      - name: setup go
 | 
					      - name: Set up Go
 | 
				
			||||||
        uses: actions/setup-go@v5
 | 
					        uses: actions/setup-go@v5
 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          go-version-file: 'go.mod'
 | 
					          go-version: stable
 | 
				
			||||||
      - name: release-build
 | 
					      - name: Run GoReleaser
 | 
				
			||||||
        run: go build -ldflags="-s -w -X 'main.Version=${{  gitea.ref_name }}'" -o bin/mcp-gitea-${{  gitea.ref_name }}-linux-amd64
 | 
					        uses: goreleaser/goreleaser-action@v6
 | 
				
			||||||
      - name: release-build-windows
 | 
					 | 
				
			||||||
        run: GOOS=windows GOARCH=amd64 go build -ldflags="-s -w -X 'main.Version=${{  gitea.ref_name }}'" -o bin/mcp-gitea-${{  gitea.ref_name }}-windows-amd64.exe
 | 
					 | 
				
			||||||
      - name: release-build-darwin
 | 
					 | 
				
			||||||
        run: GOOS=darwin GOARCH=amd64 go build -ldflags="-s -w -X 'main.Version=${{  gitea.ref_name }}'" -o bin/mcp-gitea-${{  gitea.ref_name }}-darwin-amd64
 | 
					 | 
				
			||||||
      - name: release-build-arm64
 | 
					 | 
				
			||||||
        run: GOARCH=arm64 go build -ldflags="-s -w -X 'main.Version=${{  gitea.ref_name }}'" -o bin/mcp-gitea-${{  gitea.ref_name }}-linux-arm64
 | 
					 | 
				
			||||||
      - name: release-build-windows-arm64
 | 
					 | 
				
			||||||
        run: GOOS=windows GOARCH=arm64 go build -ldflags="-s -w -X 'main.Version=${{  gitea.ref_name }}'" -o bin/mcp-gitea-${{  gitea.ref_name }}-windows-arm64.exe
 | 
					 | 
				
			||||||
      - name: release-build-darwin-arm64
 | 
					 | 
				
			||||||
        run: GOOS=darwin GOARCH=arm64 go build -ldflags="-s -w -X 'main.Version=${{  gitea.ref_name }}'" -o bin/mcp-gitea-${{  gitea.ref_name }}-darwin-arm64
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      - name: Use Go Action  
 | 
					 | 
				
			||||||
        id: use-go-action
 | 
					 | 
				
			||||||
        uses: https://gitea.com/actions/gitea-release-action@main
 | 
					 | 
				
			||||||
        with:
 | 
					        with:
 | 
				
			||||||
          files: |-
 | 
					          distribution: goreleaser
 | 
				
			||||||
            bin/**
 | 
					          # 'latest', 'nightly', or a semver
 | 
				
			||||||
          token: '${{secrets.RELEASE_TOKEN}}'
 | 
					          version: "~> v2"
 | 
				
			||||||
 | 
					          args: release --clean
 | 
				
			||||||
 | 
					        env:
 | 
				
			||||||
 | 
					          GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 | 
				
			||||||
 | 
					          GORELEASER_FORCE_TOKEN: "gitea"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  release-image:
 | 
					  release-image:
 | 
				
			||||||
    runs-on: ubuntu-latest
 | 
					    runs-on: ubuntu-latest
 | 
				
			||||||
    env:
 | 
					    env:
 | 
				
			||||||
@@ -74,6 +66,8 @@ jobs:
 | 
				
			|||||||
            linux/amd64
 | 
					            linux/amd64
 | 
				
			||||||
            linux/arm64
 | 
					            linux/arm64
 | 
				
			||||||
          push: true
 | 
					          push: true
 | 
				
			||||||
 | 
					          build-args: |
 | 
				
			||||||
 | 
					            VERSION=${{ steps.meta.outputs.REPO_VERSION }}
 | 
				
			||||||
          tags: |
 | 
					          tags: |
 | 
				
			||||||
            ${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}:${{ steps.meta.outputs.REPO_VERSION }}
 | 
					            ${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}-server:${{ steps.meta.outputs.REPO_VERSION }}
 | 
				
			||||||
            ${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}-server:${{ env.DOCKER_LATEST }}
 | 
					            ${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}-server:${{ env.DOCKER_LATEST }}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,7 +1,5 @@
 | 
				
			|||||||
.idea
 | 
					.idea
 | 
				
			||||||
.vscode
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
gitea-mcp
 | 
					gitea-mcp
 | 
				
			||||||
gitea-mcp.exe
 | 
					gitea-mcp.exe
 | 
				
			||||||
 | 
					 | 
				
			||||||
*.log
 | 
					*.log
 | 
				
			||||||
 | 
					tmp
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										76
									
								
								.goreleaser.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								.goreleaser.yaml
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,76 @@
 | 
				
			|||||||
 | 
					# yaml-language-server: $schema=https://goreleaser.com/static/schema.json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					version: 2
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					before:
 | 
				
			||||||
 | 
					  hooks:
 | 
				
			||||||
 | 
					    - go mod tidy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					builds:
 | 
				
			||||||
 | 
					  - env:
 | 
				
			||||||
 | 
					      - CGO_ENABLED=0
 | 
				
			||||||
 | 
					    main: .
 | 
				
			||||||
 | 
					    goos:
 | 
				
			||||||
 | 
					      - linux
 | 
				
			||||||
 | 
					      - windows
 | 
				
			||||||
 | 
					      - darwin
 | 
				
			||||||
 | 
					    flags:
 | 
				
			||||||
 | 
					      - -trimpath
 | 
				
			||||||
 | 
					    ldflags:
 | 
				
			||||||
 | 
					      - -s -w
 | 
				
			||||||
 | 
					      - -X main.Version={{.Version}}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					archives:
 | 
				
			||||||
 | 
					  - formats: tar.gz
 | 
				
			||||||
 | 
					    # this name template makes the OS and Arch compatible with the results of `uname`.
 | 
				
			||||||
 | 
					    name_template: >-
 | 
				
			||||||
 | 
					      {{ .ProjectName }}_
 | 
				
			||||||
 | 
					      {{- title .Os }}_
 | 
				
			||||||
 | 
					      {{- if eq .Arch "amd64" }}x86_64
 | 
				
			||||||
 | 
					      {{- else if eq .Arch "386" }}i386
 | 
				
			||||||
 | 
					      {{- else }}{{ .Arch }}{{ end }}
 | 
				
			||||||
 | 
					      {{- if .Arm }}v{{ .Arm }}{{ end }}
 | 
				
			||||||
 | 
					    # use zip for windows archives
 | 
				
			||||||
 | 
					    format_overrides:
 | 
				
			||||||
 | 
					      - goos: windows
 | 
				
			||||||
 | 
					        formats: zip
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					changelog:
 | 
				
			||||||
 | 
					  sort: asc
 | 
				
			||||||
 | 
					  groups:
 | 
				
			||||||
 | 
					    - title: Features
 | 
				
			||||||
 | 
					      regexp: "^.*feat[(\\w)]*:+.*$"
 | 
				
			||||||
 | 
					      order: 0
 | 
				
			||||||
 | 
					    - title: "Bug fixes"
 | 
				
			||||||
 | 
					      regexp: "^.*fix[(\\w)]*:+.*$"
 | 
				
			||||||
 | 
					      order: 1
 | 
				
			||||||
 | 
					    - title: "Enhancements"
 | 
				
			||||||
 | 
					      regexp: "^.*chore[(\\w)]*:+.*$"
 | 
				
			||||||
 | 
					      order: 2
 | 
				
			||||||
 | 
					    - title: "Refactor"
 | 
				
			||||||
 | 
					      regexp: "^.*refactor[(\\w)]*:+.*$"
 | 
				
			||||||
 | 
					      order: 3
 | 
				
			||||||
 | 
					    - title: "Build process updates"
 | 
				
			||||||
 | 
					      regexp: ^.*?(build|ci)(\(.+\))??!?:.+$
 | 
				
			||||||
 | 
					      order: 4
 | 
				
			||||||
 | 
					    - title: "Documentation updates"
 | 
				
			||||||
 | 
					      regexp: ^.*?docs?(\(.+\))??!?:.+$
 | 
				
			||||||
 | 
					      order: 4
 | 
				
			||||||
 | 
					    - title: Others
 | 
				
			||||||
 | 
					      order: 999
 | 
				
			||||||
 | 
					  filters:
 | 
				
			||||||
 | 
					    exclude:
 | 
				
			||||||
 | 
					      - "^docs:"
 | 
				
			||||||
 | 
					      - "^test:"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					release:
 | 
				
			||||||
 | 
					  footer: >-
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    ---
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    Released by [GoReleaser](https://github.com/goreleaser/goreleaser).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					gitea_urls:
 | 
				
			||||||
 | 
					  api: https://gitea.com/api/v1
 | 
				
			||||||
 | 
					  download: https://gitea.com
 | 
				
			||||||
 | 
					force_token: gitea
 | 
				
			||||||
							
								
								
									
										39
									
								
								.vscode/mcp.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								.vscode/mcp.json
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,39 @@
 | 
				
			|||||||
 | 
					{
 | 
				
			||||||
 | 
					  // 💡 Inputs are prompted on first server start, then stored securely by VS Code.
 | 
				
			||||||
 | 
					  "inputs": [
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "type": "promptString",
 | 
				
			||||||
 | 
					      "id": "gitea-host",
 | 
				
			||||||
 | 
					      "description": "Gitea Host",
 | 
				
			||||||
 | 
					      "password": false
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "type": "promptString",
 | 
				
			||||||
 | 
					      "id": "gitea-token",
 | 
				
			||||||
 | 
					      "description": "Gitea Access Token",
 | 
				
			||||||
 | 
					      "password": true
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      "type": "promptString",
 | 
				
			||||||
 | 
					      "id": "gitea-insecure",
 | 
				
			||||||
 | 
					      "description": "Allow insecure connections (e.g., self-signed certificates)",
 | 
				
			||||||
 | 
					      "default": "false"
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  ],
 | 
				
			||||||
 | 
					  "servers": {
 | 
				
			||||||
 | 
					    "gitea-mcp-stdio": {
 | 
				
			||||||
 | 
					      "type": "stdio",
 | 
				
			||||||
 | 
					      "command": "gitea-mcp",
 | 
				
			||||||
 | 
					      "args": ["-t", "stdio"],
 | 
				
			||||||
 | 
					          "env": {
 | 
				
			||||||
 | 
					            "GITEA_HOST": "${input:gitea-host}",
 | 
				
			||||||
 | 
					            "GITEA_ACCESS_TOKEN": "${input:gitea-token}",
 | 
				
			||||||
 | 
					            "GITEA_INSECURE": "${input:gitea-insecure}"
 | 
				
			||||||
 | 
					          }
 | 
				
			||||||
 | 
					    },
 | 
				
			||||||
 | 
					    "gitea-mcp-http": {
 | 
				
			||||||
 | 
					      "type": "http",
 | 
				
			||||||
 | 
					      "url": "http://localhost:8080/mcp",
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
							
								
								
									
										38
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										38
									
								
								Dockerfile
									
									
									
									
									
								
							@@ -1,18 +1,32 @@
 | 
				
			|||||||
FROM golang:1.24-alpine AS builder
 | 
					# syntax=docker/dockerfile:1.4
 | 
				
			||||||
 | 
					
 | 
				
			||||||
ARG VERSION
 | 
					# Build stage
 | 
				
			||||||
 | 
					FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder
 | 
				
			||||||
 | 
					
 | 
				
			||||||
WORKDIR /build
 | 
					ARG VERSION=dev
 | 
				
			||||||
 | 
					ARG TARGETOS
 | 
				
			||||||
COPY . .
 | 
					ARG TARGETARCH
 | 
				
			||||||
RUN go mod download
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RUN CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=${VERSION}" -o gitea-mcp
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
FROM scratch
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
WORKDIR /app
 | 
					WORKDIR /app
 | 
				
			||||||
 | 
					
 | 
				
			||||||
COPY --from=builder /build/gitea-mcp .
 | 
					COPY go.mod go.sum ./
 | 
				
			||||||
 | 
					RUN --mount=type=cache,target=/go/pkg/mod \
 | 
				
			||||||
 | 
					    go mod download
 | 
				
			||||||
 | 
					
 | 
				
			||||||
CMD ["./gitea-mcp", "-t", "stdio"]
 | 
					COPY . .
 | 
				
			||||||
 | 
					RUN --mount=type=cache,target=/go/pkg/mod \
 | 
				
			||||||
 | 
					    --mount=type=cache,target=/root/.cache/go-build \
 | 
				
			||||||
 | 
					    CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH:-amd64} \
 | 
				
			||||||
 | 
					    go build -trimpath -ldflags="-s -w -X main.Version=${VERSION}" -o gitea-mcp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# Final stage
 | 
				
			||||||
 | 
					FROM gcr.io/distroless/static-debian12:nonroot
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					WORKDIR /app
 | 
				
			||||||
 | 
					COPY --from=builder --chown=nonroot:nonroot /app/gitea-mcp .
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					USER nonroot:nonroot
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					LABEL org.opencontainers.image.version="${VERSION}"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					CMD ["/app/gitea-mcp"]
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										39
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								Makefile
									
									
									
									
									
								
							@@ -3,25 +3,50 @@ EXECUTABLE := gitea-mcp
 | 
				
			|||||||
VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')
 | 
					VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')
 | 
				
			||||||
LDFLAGS := -X "main.Version=$(VERSION)"
 | 
					LDFLAGS := -X "main.Version=$(VERSION)"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.PHONY: help
 | 
				
			||||||
 | 
					help: ## Print this help message.
 | 
				
			||||||
 | 
						@echo "Usage: make [target]"
 | 
				
			||||||
 | 
						@echo ""
 | 
				
			||||||
 | 
						@echo "Targets:"
 | 
				
			||||||
 | 
						@echo ""
 | 
				
			||||||
 | 
						@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.PHONY: install
 | 
				
			||||||
 | 
					install: build ## Install the application.
 | 
				
			||||||
 | 
						@echo "Installing $(EXECUTABLE)..."
 | 
				
			||||||
 | 
						@mkdir -p $(GOPATH)/bin
 | 
				
			||||||
 | 
						@cp $(EXECUTABLE) $(GOPATH)/bin/$(EXECUTABLE)
 | 
				
			||||||
 | 
						@echo "Installed $(EXECUTABLE) to $(GOPATH)/bin/$(EXECUTABLE)"
 | 
				
			||||||
 | 
						@echo "Please add $(GOPATH)/bin to your PATH if it is not already there."
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.PHONY: uninstall
 | 
				
			||||||
 | 
					uninstall: ## Uninstall the application.
 | 
				
			||||||
 | 
						@echo "Uninstalling $(EXECUTABLE)..."
 | 
				
			||||||
 | 
						@rm -f $(GOPATH)/bin/$(EXECUTABLE)
 | 
				
			||||||
 | 
						@echo "Uninstalled $(EXECUTABLE) from $(GOPATH)/bin/$(EXECUTABLE)"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					.PHONY: clean
 | 
				
			||||||
 | 
					clean: ## Clean the build artifacts.
 | 
				
			||||||
 | 
						@echo "Cleaning up build artifacts..."
 | 
				
			||||||
 | 
						@rm -f $(EXECUTABLE)
 | 
				
			||||||
 | 
						@echo "Cleaned up $(EXECUTABLE)"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.PHONY: build
 | 
					.PHONY: build
 | 
				
			||||||
build:
 | 
					build: ## Build the application.
 | 
				
			||||||
	$(GO) build -v -ldflags '-s -w $(LDFLAGS)' -o $(EXECUTABLE)
 | 
						$(GO) build -v -ldflags '-s -w $(LDFLAGS)' -o $(EXECUTABLE)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## air: install air for hot reload
 | 
					 | 
				
			||||||
.PHONY: air
 | 
					.PHONY: air
 | 
				
			||||||
air:
 | 
					air: ## Install air for hot reload.
 | 
				
			||||||
	@hash air > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
 | 
						@hash air > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
 | 
				
			||||||
		$(GO) install github.com/air-verse/air@latest; \
 | 
							$(GO) install github.com/air-verse/air@latest; \
 | 
				
			||||||
	fi
 | 
						fi
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## dev: run the application with hot reload
 | 
					 | 
				
			||||||
.PHONY: dev
 | 
					.PHONY: dev
 | 
				
			||||||
dev: air
 | 
					dev: air ## run the application with hot reload
 | 
				
			||||||
	air --build.cmd "make build" --build.bin ./gitea-mcp
 | 
						air --build.cmd "make build" --build.bin ./gitea-mcp
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## vendor: tidy and verify module dependencies
 | 
					 | 
				
			||||||
.PHONY: vendor
 | 
					.PHONY: vendor
 | 
				
			||||||
vendor:
 | 
					vendor: ## tidy and verify module dependencies
 | 
				
			||||||
	@echo 'Tidying and verifying module dependencies...'
 | 
						@echo 'Tidying and verifying module dependencies...'
 | 
				
			||||||
	go mod tidy
 | 
						go mod tidy
 | 
				
			||||||
	go mod verify
 | 
						go mod verify
 | 
				
			||||||
							
								
								
									
										67
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										67
									
								
								README.md
									
									
									
									
									
								
							@@ -1,8 +1,26 @@
 | 
				
			|||||||
# Gitea MCP Server
 | 
					# Gitea MCP Server
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[繁體中文](README.zh-tw.md) | [简体中文](README.zh-cn.md)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
**Gitea MCP Server** is an integration plugin designed to connect Gitea with Model Context Protocol (MCP) systems. This allows for seamless command execution and repository management through an MCP-compatible chat interface.
 | 
					**Gitea MCP Server** is an integration plugin designed to connect Gitea with Model Context Protocol (MCP) systems. This allows for seamless command execution and repository management through an MCP-compatible chat interface.
 | 
				
			||||||
 | 
					
 | 
				
			||||||
[](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22gitea/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}) [](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22gitea/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}&quality=insiders)
 | 
					[](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}) [](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}&quality=insiders)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## Table of Contents
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [Gitea MCP Server](#gitea-mcp-server)
 | 
				
			||||||
 | 
					  - [Table of Contents](#table-of-contents)
 | 
				
			||||||
 | 
					  - [What is Gitea?](#what-is-gitea)
 | 
				
			||||||
 | 
					  - [What is MCP?](#what-is-mcp)
 | 
				
			||||||
 | 
					  - [🚧 Installation](#-installation)
 | 
				
			||||||
 | 
					    - [Usage with VS Code](#usage-with-vs-code)
 | 
				
			||||||
 | 
					    - [📥 Download the official binary release](#-download-the-official-binary-release)
 | 
				
			||||||
 | 
					    - [🔧 Build from Source](#-build-from-source)
 | 
				
			||||||
 | 
					    - [📁 Add to PATH](#-add-to-path)
 | 
				
			||||||
 | 
					  - [🚀 Usage](#-usage)
 | 
				
			||||||
 | 
					  - [✅ Available Tools](#-available-tools)
 | 
				
			||||||
 | 
					  - [🐛 Debugging](#-debugging)
 | 
				
			||||||
 | 
					  - [🛠 Troubleshooting](#-troubleshooting)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
## What is Gitea?
 | 
					## What is Gitea?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -36,7 +54,7 @@ Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace
 | 
				
			|||||||
      }
 | 
					      }
 | 
				
			||||||
    ],
 | 
					    ],
 | 
				
			||||||
    "servers": {
 | 
					    "servers": {
 | 
				
			||||||
      "github": {
 | 
					      "gitea-mcp": {
 | 
				
			||||||
        "command": "docker",
 | 
					        "command": "docker",
 | 
				
			||||||
        "args": [
 | 
					        "args": [
 | 
				
			||||||
          "run",
 | 
					          "run",
 | 
				
			||||||
@@ -44,7 +62,7 @@ Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace
 | 
				
			|||||||
          "--rm",
 | 
					          "--rm",
 | 
				
			||||||
          "-e",
 | 
					          "-e",
 | 
				
			||||||
          "GITEA_ACCESS_TOKEN",
 | 
					          "GITEA_ACCESS_TOKEN",
 | 
				
			||||||
          "gitea/gitea-mcp-server"
 | 
					          "docker.gitea.com/gitea-mcp-server"
 | 
				
			||||||
        ],
 | 
					        ],
 | 
				
			||||||
        "env": {
 | 
					        "env": {
 | 
				
			||||||
          "GITEA_ACCESS_TOKEN": "${input:gitea_token}"
 | 
					          "GITEA_ACCESS_TOKEN": "${input:gitea_token}"
 | 
				
			||||||
@@ -57,7 +75,7 @@ Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
### 📥 Download the official binary release
 | 
					### 📥 Download the official binary release
 | 
				
			||||||
 | 
					
 | 
				
			||||||
You can download the official release from [here](https://gitea.com/gitea/gitea-mcp/releases).
 | 
					You can download the official release from [official Gitea MCP binary releases](https://gitea.com/gitea/gitea-mcp/releases).
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 🔧 Build from Source
 | 
					### 🔧 Build from Source
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -75,12 +93,12 @@ Before building, make sure you have the following installed:
 | 
				
			|||||||
Then run:
 | 
					Then run:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
make build
 | 
					make install
 | 
				
			||||||
```
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
### 📁 Add to PATH
 | 
					### 📁 Add to PATH
 | 
				
			||||||
 | 
					
 | 
				
			||||||
After building, copy the binary gitea-mcp to a directory included in your system's PATH. For example:
 | 
					After installing, copy the binary gitea-mcp to a directory included in your system's PATH. For example:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
```bash
 | 
					```bash
 | 
				
			||||||
cp gitea-mcp /usr/local/bin/
 | 
					cp gitea-mcp /usr/local/bin/
 | 
				
			||||||
@@ -107,6 +125,7 @@ To configure the MCP server for Gitea, add the following to your MCP configurati
 | 
				
			|||||||
      ],
 | 
					      ],
 | 
				
			||||||
      "env": {
 | 
					      "env": {
 | 
				
			||||||
        // "GITEA_HOST": "https://gitea.com",
 | 
					        // "GITEA_HOST": "https://gitea.com",
 | 
				
			||||||
 | 
					        // "GITEA_INSECURE": "true",
 | 
				
			||||||
        "GITEA_ACCESS_TOKEN": "<your personal access token>"
 | 
					        "GITEA_ACCESS_TOKEN": "<your personal access token>"
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
@@ -120,7 +139,25 @@ To configure the MCP server for Gitea, add the following to your MCP configurati
 | 
				
			|||||||
{
 | 
					{
 | 
				
			||||||
  "mcpServers": {
 | 
					  "mcpServers": {
 | 
				
			||||||
    "gitea": {
 | 
					    "gitea": {
 | 
				
			||||||
      "url": "http://localhost:8080/sse"
 | 
					      "url": "http://localhost:8080/sse",
 | 
				
			||||||
 | 
					      "headers": {
 | 
				
			||||||
 | 
					        "Authorization": "Bearer <your personal access token>"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- **http mode**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "mcpServers": {
 | 
				
			||||||
 | 
					    "gitea": {
 | 
				
			||||||
 | 
					      "url": "http://localhost:8080/mcp",
 | 
				
			||||||
 | 
					      "headers": {
 | 
				
			||||||
 | 
					        "Authorization": "Bearer <your personal access token>"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -143,16 +180,27 @@ list all my repositories
 | 
				
			|||||||
The Gitea MCP Server supports the following tools:
 | 
					The Gitea MCP Server supports the following tools:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
|             Tool             |    Scope     |                       Description                        |
 | 
					|             Tool             |    Scope     |                       Description                        |
 | 
				
			||||||
| :--------------------------: | :----------: | :---------------------------------------------------: |
 | 
					| :--------------------------: | :----------: | :------------------------------------------------------: |
 | 
				
			||||||
|       get_my_user_info       |     User     |      Get the information of the authenticated user       |
 | 
					|       get_my_user_info       |     User     |      Get the information of the authenticated user       |
 | 
				
			||||||
 | 
					|        get_user_orgs         |     User     | Get organizations associated with the authenticated user |
 | 
				
			||||||
|         create_repo          |  Repository  |                 Create a new repository                  |
 | 
					|         create_repo          |  Repository  |                 Create a new repository                  |
 | 
				
			||||||
|          fork_repo           |  Repository  |                    Fork a repository                     |
 | 
					|          fork_repo           |  Repository  |                    Fork a repository                     |
 | 
				
			||||||
|        list_my_repos         |  Repository  |  List all repositories owned by the authenticated user   |
 | 
					|        list_my_repos         |  Repository  |  List all repositories owned by the authenticated user   |
 | 
				
			||||||
|        create_branch         |    Branch    |                   Create a new branch                    |
 | 
					|        create_branch         |    Branch    |                   Create a new branch                    |
 | 
				
			||||||
|        delete_branch         |    Branch    |                     Delete a branch                      |
 | 
					|        delete_branch         |    Branch    |                     Delete a branch                      |
 | 
				
			||||||
|        list_branches         |    Branch    |            List all branches in a repository             |
 | 
					|        list_branches         |    Branch    |            List all branches in a repository             |
 | 
				
			||||||
 | 
					|        create_release        |   Release    |           Create a new release in a repository           |
 | 
				
			||||||
 | 
					|        delete_release        |   Release    |            Delete a release from a repository            |
 | 
				
			||||||
 | 
					|         get_release          |   Release    |                      Get a release                       |
 | 
				
			||||||
 | 
					|      get_latest_release      |   Release    |          Get the latest release in a repository          |
 | 
				
			||||||
 | 
					|        list_releases         |   Release    |            List all releases in a repository             |
 | 
				
			||||||
 | 
					|          create_tag          |     Tag      |                     Create a new tag                     |
 | 
				
			||||||
 | 
					|          delete_tag          |     Tag      |                       Delete a tag                       |
 | 
				
			||||||
 | 
					|           get_tag            |     Tag      |                        Get a tag                         |
 | 
				
			||||||
 | 
					|          list_tags           |     Tag      |              List all tags in a repository               |
 | 
				
			||||||
|      list_repo_commits       |    Commit    |             List all commits in a repository             |
 | 
					|      list_repo_commits       |    Commit    |             List all commits in a repository             |
 | 
				
			||||||
|       get_file_content       |     File     |          Get the content and metadata of a file          |
 | 
					|       get_file_content       |     File     |          Get the content and metadata of a file          |
 | 
				
			||||||
 | 
					|        get_dir_content       |     File     |           Get a list of entries in a directory           |
 | 
				
			||||||
|         create_file          |     File     |                    Create a new file                     |
 | 
					|         create_file          |     File     |                    Create a new file                     |
 | 
				
			||||||
|         update_file          |     File     |                 Update an existing file                  |
 | 
					|         update_file          |     File     |                 Update an existing file                  |
 | 
				
			||||||
|         delete_file          |     File     |                      Delete a file                       |
 | 
					|         delete_file          |     File     |                      Delete a file                       |
 | 
				
			||||||
@@ -160,6 +208,9 @@ The Gitea MCP Server supports the following tools:
 | 
				
			|||||||
|       list_repo_issues       |    Issue     |             List all issues in a repository              |
 | 
					|       list_repo_issues       |    Issue     |             List all issues in a repository              |
 | 
				
			||||||
|         create_issue         |    Issue     |                    Create a new issue                    |
 | 
					|         create_issue         |    Issue     |                    Create a new issue                    |
 | 
				
			||||||
|     create_issue_comment     |    Issue     |               Create a comment on an issue               |
 | 
					|     create_issue_comment     |    Issue     |               Create a comment on an issue               |
 | 
				
			||||||
 | 
					|          edit_issue          |    Issue     |                       Edit a issue                       |
 | 
				
			||||||
 | 
					|      edit_issue_comment      |    Issue     |                Edit a comment on an issue                |
 | 
				
			||||||
 | 
					| get_issue_comments_by_index  |    Issue     |          Get comments of an issue by its index           |
 | 
				
			||||||
|  get_pull_request_by_index   | Pull Request |             Get a pull request by its index              |
 | 
					|  get_pull_request_by_index   | Pull Request |             Get a pull request by its index              |
 | 
				
			||||||
|   list_repo_pull_requests    | Pull Request |          List all pull requests in a repository          |
 | 
					|   list_repo_pull_requests    | Pull Request |          List all pull requests in a repository          |
 | 
				
			||||||
|     create_pull_request      | Pull Request |                Create a new pull request                 |
 | 
					|     create_pull_request      | Pull Request |                Create a new pull request                 |
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										239
									
								
								README.zh-cn.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										239
									
								
								README.zh-cn.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,239 @@
 | 
				
			|||||||
 | 
					# Gitea MCP 服务器
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[English](README.md) | [繁體中文](README.zh-tw.md)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Gitea MCP 服务器** 是一个集成插件,旨在将 Gitea 与 Model Context Protocol (MCP) 系统连接起来。这允许通过 MCP 兼容的聊天界面无缝执行命令和管理仓库。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}) [](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}&quality=insiders)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 目录
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [Gitea MCP 服务器](#gitea-mcp-服务器)
 | 
				
			||||||
 | 
					  - [目录](#目录)
 | 
				
			||||||
 | 
					  - [什么是 Gitea?](#什么是-gitea)
 | 
				
			||||||
 | 
					  - [什么是 MCP?](#什么是-mcp)
 | 
				
			||||||
 | 
					  - [🚧 安装](#-安装)
 | 
				
			||||||
 | 
					    - [在 VS Code 中使用](#在-vs-code-中使用)
 | 
				
			||||||
 | 
					    - [📥 下载官方 Gitea MCP 二进制版本](#-下载官方-gitea-mcp-二进制版本)
 | 
				
			||||||
 | 
					    - [🔧 从源代码构建](#-从源代码构建)
 | 
				
			||||||
 | 
					    - [📁 添加到 PATH](#-添加到-path)
 | 
				
			||||||
 | 
					  - [🚀 使用](#-使用)
 | 
				
			||||||
 | 
					  - [✅ 可用工具](#-可用工具)
 | 
				
			||||||
 | 
					  - [🐛 调试](#-调试)
 | 
				
			||||||
 | 
					  - [🛠 疑难排解](#-疑难排解)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 什么是 Gitea?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Gitea 是一个由社区管理的轻量级代码托管解决方案,使用 Go 语言编写。它以 MIT 许可证发布。Gitea 提供 Git 托管,包括仓库查看器、问题追踪、拉取请求等功能。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 什么是 MCP?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Model Context Protocol (MCP) 是一种协议,允许通过聊天界面整合各种工具和系统。它能够无缝执行命令和管理仓库、用户和其他资源。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 🚧 安装
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 在 VS Code 中使用
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					要快速安装,请使用本 README 顶部的单击安装按钮之一。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					要手动安装,请将以下 JSON 块添加到 VS Code 的用户设置 (JSON) 文件中。您可以通过按 `Ctrl + Shift + P` 并输入 `Preferences: Open User Settings (JSON)` 来完成此操作。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					或者,您可以将其添加到工作区中的 `.vscode/mcp.json` 文件中。这将允许您与他人共享配置。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> 请注意,`.vscode/mcp.json` 文件中不需要 `mcp` 键。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "mcp": {
 | 
				
			||||||
 | 
					    "inputs": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "type": "promptString",
 | 
				
			||||||
 | 
					        "id": "gitea_token",
 | 
				
			||||||
 | 
					        "description": "Gitea 个人访问令牌",
 | 
				
			||||||
 | 
					        "password": true
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "servers": {
 | 
				
			||||||
 | 
					      "gitea-mcp": {
 | 
				
			||||||
 | 
					        "command": "docker",
 | 
				
			||||||
 | 
					        "args": [
 | 
				
			||||||
 | 
					          "run",
 | 
				
			||||||
 | 
					          "-i",
 | 
				
			||||||
 | 
					          "--rm",
 | 
				
			||||||
 | 
					          "-e",
 | 
				
			||||||
 | 
					          "GITEA_ACCESS_TOKEN",
 | 
				
			||||||
 | 
					          "docker.gitea.com/gitea-mcp-server"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "env": {
 | 
				
			||||||
 | 
					          "GITEA_ACCESS_TOKEN": "${input:gitea_token}"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 📥 下载官方 Gitea MCP 二进制版本
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					您可以从[官方 Gitea MCP 二进制版本](https://gitea.com/gitea/gitea-mcp/releases)下载官方版本。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 🔧 从源代码构建
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					您可以使用 Git 克隆仓库来下载源代码:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					git clone https://gitea.com/gitea/gitea-mcp.git
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					在构建之前,请确保您已安装以下内容:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- make
 | 
				
			||||||
 | 
					- Golang (建议使用 Go 1.24 或更高版本)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					然后运行:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					make install
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 📁 添加到 PATH
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					构建后,将二进制文件 gitea-mcp 复制到系统 PATH 中包含的目录。例如:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					cp gitea-mcp /usr/local/bin/
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 🚀 使用
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					此示例适用于 Cursor,您也可以在 VSCode 中使用插件。
 | 
				
			||||||
 | 
					要配置 Gitea 的 MCP 服务器,请将以下内容添加到您的 MCP 配置文件中:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- **stdio 模式**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "mcpServers": {
 | 
				
			||||||
 | 
					    "gitea": {
 | 
				
			||||||
 | 
					      "command": "gitea-mcp",
 | 
				
			||||||
 | 
					      "args": [
 | 
				
			||||||
 | 
					        "-t",
 | 
				
			||||||
 | 
					        "stdio",
 | 
				
			||||||
 | 
					        "--host",
 | 
				
			||||||
 | 
					        "https://gitea.com"
 | 
				
			||||||
 | 
					        // "--token", "<your personal access token>"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "env": {
 | 
				
			||||||
 | 
					        // "GITEA_HOST": "https://gitea.com",
 | 
				
			||||||
 | 
					        // "GITEA_INSECURE": "true",
 | 
				
			||||||
 | 
					        "GITEA_ACCESS_TOKEN": "<your personal access token>"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- **sse 模式**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "mcpServers": {
 | 
				
			||||||
 | 
					    "gitea": {
 | 
				
			||||||
 | 
					      "url": "http://localhost:8080/sse",
 | 
				
			||||||
 | 
					      "headers": {
 | 
				
			||||||
 | 
					        "Authorization": "Bearer <your personal access token>"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- **http 模式**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "mcpServers": {
 | 
				
			||||||
 | 
					    "gitea": {
 | 
				
			||||||
 | 
					      "url": "http://localhost:8080/mcp",
 | 
				
			||||||
 | 
					      "headers": {
 | 
				
			||||||
 | 
					        "Authorization": "Bearer <your personal access token>"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**默认日志路径**: `$HOME/.gitea-mcp/gitea-mcp.log`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> [!注意]
 | 
				
			||||||
 | 
					> 您可以通过命令行参数或环境变量提供您的 Gitea 主机和访问令牌。
 | 
				
			||||||
 | 
					> 命令行参数具有最高优先级
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					一切设置完成后,请尝试在您的 MCP 兼容聊天框中输入以下内容:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```text
 | 
				
			||||||
 | 
					列出我所有的仓库
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## ✅ 可用工具
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Gitea MCP 服务器支持以下工具:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					|             工具             |   范围   |             描述             |
 | 
				
			||||||
 | 
					| :--------------------------: | :------: | :--------------------------: |
 | 
				
			||||||
 | 
					|       get_my_user_info       |   用户   |     获取已认证用户的信息     |
 | 
				
			||||||
 | 
					|        get_user_orgs         |   用户   |   获取已认证用户关联的组织   |
 | 
				
			||||||
 | 
					|         create_repo          |   仓库   |        创建一个新仓库        |
 | 
				
			||||||
 | 
					|          fork_repo           |   仓库   |         复刻一个仓库         |
 | 
				
			||||||
 | 
					|        list_my_repos         |   仓库   | 列出已认证用户拥有的所有仓库 |
 | 
				
			||||||
 | 
					|        create_branch         |   分支   |        创建一个新分支        |
 | 
				
			||||||
 | 
					|        delete_branch         |   分支   |         删除一个分支         |
 | 
				
			||||||
 | 
					|        list_branches         |   分支   |     列出仓库中的所有分支     |
 | 
				
			||||||
 | 
					|        create_release        | 版本发布 |      创建一个新版本发布      |
 | 
				
			||||||
 | 
					|        delete_release        | 版本发布 |       删除一个版本发布       |
 | 
				
			||||||
 | 
					|         get_release          | 版本发布 |       获取一个版本发布       |
 | 
				
			||||||
 | 
					|      get_latest_release      | 版本发布 |      获取最新的版本发布      |
 | 
				
			||||||
 | 
					|        list_releases         | 版本发布 |       列出所有版本发布       |
 | 
				
			||||||
 | 
					|          create_tag          |   标签   |        创建一个新标签        |
 | 
				
			||||||
 | 
					|          delete_tag          |   标签   |         删除一个标签         |
 | 
				
			||||||
 | 
					|           get_tag            |   标签   |         获取一个标签         |
 | 
				
			||||||
 | 
					|          list_tags           |   标签   |         列出所有标签         |
 | 
				
			||||||
 | 
					|      list_repo_commits       |   提交   |     列出仓库中的所有提交     |
 | 
				
			||||||
 | 
					|       get_file_content       |   文件   |    获取文件的内容和元数据    |
 | 
				
			||||||
 | 
					|        get_dir_content       |   文件   |      获取目录的内容列表      |
 | 
				
			||||||
 | 
					|         create_file          |   文件   |        创建一个新文件        |
 | 
				
			||||||
 | 
					|         update_file          |   文件   |         更新现有文件         |
 | 
				
			||||||
 | 
					|         delete_file          |   文件   |         删除一个文件         |
 | 
				
			||||||
 | 
					|      get_issue_by_index      |   问题   |       根据索引获取问题       |
 | 
				
			||||||
 | 
					|       list_repo_issues       |   问题   |     列出仓库中的所有问题     |
 | 
				
			||||||
 | 
					|         create_issue         |   问题   |        创建一个新问题        |
 | 
				
			||||||
 | 
					|     create_issue_comment     |   问题   |       在问题上创建评论       |
 | 
				
			||||||
 | 
					|          edit_issue          |   问题   |         编辑一个问题         |
 | 
				
			||||||
 | 
					|      edit_issue_comment      |   问题   |      在问题上编辑评论         |
 | 
				
			||||||
 | 
					| get_issue_comments_by_index  |   问题   |     根据索引获取问题的评论     |
 | 
				
			||||||
 | 
					|  get_pull_request_by_index   | 拉取请求 |     根据索引获取拉取请求     |
 | 
				
			||||||
 | 
					|   list_repo_pull_requests    | 拉取请求 |   列出仓库中的所有拉取请求   |
 | 
				
			||||||
 | 
					|     create_pull_request      | 拉取请求 |      创建一个新拉取请求      |
 | 
				
			||||||
 | 
					|         search_users         |   用户   |           搜索用户           |
 | 
				
			||||||
 | 
					|       search_org_teams       |   组织   |       搜索组织中的团队       |
 | 
				
			||||||
 | 
					|         search_repos         |   仓库   |           搜索仓库           |
 | 
				
			||||||
 | 
					| get_gitea_mcp_server_version |   服务器    |        获取 Gitea MCP 服务器的版本         |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 🐛 调试
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					要启用调试模式,请在使用 sse 模式运行 Gitea MCP 服务器时添加 `-d` 标志:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```sh
 | 
				
			||||||
 | 
					./gitea-mcp -t sse [--port 8080] --token <your personal access token> -d
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 🛠 疑难排解
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					如果您遇到任何问题,以下是一些常见的疑难排解步骤:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. **检查您的 PATH**: 确保 `gitea-mcp` 二进制文件位于系统 PATH 中包含的目录中。
 | 
				
			||||||
 | 
					2. **验证依赖项**: 确保您已安装所有所需的依赖项,例如 `make` 和 `Golang`。
 | 
				
			||||||
 | 
					3. **检查配置**: 仔细检查您的 MCP 配置文件是否有任何错误或遗漏的信息。
 | 
				
			||||||
 | 
					4. **查看日志**: 检查日志中是否有任何错误消息或警告,可以提供有关问题的更多信息。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					享受通过聊天探索和管理您的 Gitea 仓库的乐趣!
 | 
				
			||||||
							
								
								
									
										239
									
								
								README.zh-tw.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										239
									
								
								README.zh-tw.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,239 @@
 | 
				
			|||||||
 | 
					# Gitea MCP 伺服器
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[English](README.md) | [简体中文](README.zh-cn.md)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**Gitea MCP 伺服器** 是一個整合插件,旨在將 Gitea 與 Model Context Protocol (MCP) 系統連接起來。這允許通過 MCP 兼容的聊天界面無縫執行命令和管理倉庫。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					[](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}) [](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}&quality=insiders)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 目錄
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- [Gitea MCP 伺服器](#gitea-mcp-伺服器)
 | 
				
			||||||
 | 
					  - [目錄](#目錄)
 | 
				
			||||||
 | 
					  - [什麼是 Gitea?](#什麼是-gitea)
 | 
				
			||||||
 | 
					  - [什麼是 MCP?](#什麼是-mcp)
 | 
				
			||||||
 | 
					  - [🚧 安裝](#-安裝)
 | 
				
			||||||
 | 
					    - [在 VS Code 中使用](#在-vs-code-中使用)
 | 
				
			||||||
 | 
					    - [📥 下載官方 Gitea MCP 二進位版本](#-下載官方-gitea-mcp-二進位版本)
 | 
				
			||||||
 | 
					    - [🔧 從源代碼構建](#-從源代碼構建)
 | 
				
			||||||
 | 
					    - [📁 添加到 PATH](#-添加到-path)
 | 
				
			||||||
 | 
					  - [🚀 使用](#-使用)
 | 
				
			||||||
 | 
					  - [✅ 可用工具](#-可用工具)
 | 
				
			||||||
 | 
					  - [🐛 調試](#-調試)
 | 
				
			||||||
 | 
					  - [🛠 疑難排解](#-疑難排解)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 什麼是 Gitea?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Gitea 是一個由社群管理的輕量級代碼託管解決方案,使用 Go 語言編寫。它以 MIT 許可證發布。Gitea 提供 Git 託管,包括倉庫查看器、問題追蹤、拉取請求等功能。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 什麼是 MCP?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Model Context Protocol (MCP) 是一種協議,允許通過聊天界面整合各種工具和系統。它能夠無縫執行命令和管理倉庫、用戶和其他資源。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 🚧 安裝
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 在 VS Code 中使用
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					要快速安裝,請使用本 README 頂部的單擊安裝按鈕之一。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					要手動安裝,請將以下 JSON 塊添加到 VS Code 的用戶設置 (JSON) 文件中。您可以通過按 `Ctrl + Shift + P` 並輸入 `Preferences: Open User Settings (JSON)` 來完成此操作。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					或者,您可以將其添加到工作區中的 `.vscode/mcp.json` 文件中。這將允許您與他人共享配置。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> 請注意,`.vscode/mcp.json` 文件中不需要 `mcp` 鍵。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "mcp": {
 | 
				
			||||||
 | 
					    "inputs": [
 | 
				
			||||||
 | 
					      {
 | 
				
			||||||
 | 
					        "type": "promptString",
 | 
				
			||||||
 | 
					        "id": "gitea_token",
 | 
				
			||||||
 | 
					        "description": "Gitea 個人訪問令牌",
 | 
				
			||||||
 | 
					        "password": true
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    ],
 | 
				
			||||||
 | 
					    "servers": {
 | 
				
			||||||
 | 
					      "gitea-mcp": {
 | 
				
			||||||
 | 
					        "command": "docker",
 | 
				
			||||||
 | 
					        "args": [
 | 
				
			||||||
 | 
					          "run",
 | 
				
			||||||
 | 
					          "-i",
 | 
				
			||||||
 | 
					          "--rm",
 | 
				
			||||||
 | 
					          "-e",
 | 
				
			||||||
 | 
					          "GITEA_ACCESS_TOKEN",
 | 
				
			||||||
 | 
					          "docker.gitea.com/gitea-mcp-server"
 | 
				
			||||||
 | 
					        ],
 | 
				
			||||||
 | 
					        "env": {
 | 
				
			||||||
 | 
					          "GITEA_ACCESS_TOKEN": "${input:gitea_token}"
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 📥 下載官方 Gitea MCP 二進位版本
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					您可以從[官方 Gitea MCP 二進位版本](https://gitea.com/gitea/gitea-mcp/releases)下載官方版本。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 🔧 從源代碼構建
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					您可以使用 Git 克隆倉庫來下載源代碼:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					git clone https://gitea.com/gitea/gitea-mcp.git
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					在構建之前,請確保您已安裝以下內容:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- make
 | 
				
			||||||
 | 
					- Golang (建議使用 Go 1.24 或更高版本)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					然後運行:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					make install
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					### 📁 添加到 PATH
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					安裝後,將二進制文件 gitea-mcp 複製到系統 PATH 中包含的目錄。例如:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```bash
 | 
				
			||||||
 | 
					cp gitea-mcp /usr/local/bin/
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 🚀 使用
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					此示例適用於 Cursor,您也可以在 VSCode 中使用插件。
 | 
				
			||||||
 | 
					要配置 Gitea 的 MCP 伺服器,請將以下內容添加到您的 MCP 配置文件中:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- **stdio 模式**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "mcpServers": {
 | 
				
			||||||
 | 
					    "gitea": {
 | 
				
			||||||
 | 
					      "command": "gitea-mcp",
 | 
				
			||||||
 | 
					      "args": [
 | 
				
			||||||
 | 
					        "-t",
 | 
				
			||||||
 | 
					        "stdio",
 | 
				
			||||||
 | 
					        "--host",
 | 
				
			||||||
 | 
					        "https://gitea.com"
 | 
				
			||||||
 | 
					        // "--token", "<your personal access token>"
 | 
				
			||||||
 | 
					      ],
 | 
				
			||||||
 | 
					      "env": {
 | 
				
			||||||
 | 
					        // "GITEA_HOST": "https://gitea.com",
 | 
				
			||||||
 | 
					        // "GITEA_INSECURE": "true",
 | 
				
			||||||
 | 
					        "GITEA_ACCESS_TOKEN": "<your personal access token>"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- **sse 模式**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "mcpServers": {
 | 
				
			||||||
 | 
					    "gitea": {
 | 
				
			||||||
 | 
					      "url": "http://localhost:8080/sse",
 | 
				
			||||||
 | 
					      "headers": {
 | 
				
			||||||
 | 
					        "Authorization": "Bearer <your personal access token>"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- **http 模式**
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```json
 | 
				
			||||||
 | 
					{
 | 
				
			||||||
 | 
					  "mcpServers": {
 | 
				
			||||||
 | 
					    "gitea": {
 | 
				
			||||||
 | 
					      "url": "http://localhost:8080/mcp",
 | 
				
			||||||
 | 
					      "headers": {
 | 
				
			||||||
 | 
					        "Authorization": "Bearer <your personal access token>"
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  }
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					**預設日誌路徑**: `$HOME/.gitea-mcp/gitea-mcp.log`
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					> [!注意]
 | 
				
			||||||
 | 
					> 您可以通過命令列參數或環境變數提供您的 Gitea 主機和訪問令牌。
 | 
				
			||||||
 | 
					> 命令列參數具有最高優先權
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					一切設置完成後,請嘗試在您的 MCP 兼容聊天框中輸入以下內容:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```text
 | 
				
			||||||
 | 
					列出我所有的倉庫
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## ✅ 可用工具
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Gitea MCP 伺服器支持以下工具:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					|             工具             |   範圍   |             描述             |
 | 
				
			||||||
 | 
					| :--------------------------: | :------: | :--------------------------: |
 | 
				
			||||||
 | 
					|       get_my_user_info       |   用戶   |     獲取已認證用戶的信息     |
 | 
				
			||||||
 | 
					|        get_user_orgs         |   用戶   |    取得已認證用戶所屬組織    |
 | 
				
			||||||
 | 
					|         create_repo          |   倉庫   |        創建一個新倉庫        |
 | 
				
			||||||
 | 
					|          fork_repo           |   倉庫   |         復刻一個倉庫         |
 | 
				
			||||||
 | 
					|        list_my_repos         |   倉庫   | 列出已認證用戶擁有的所有倉庫 |
 | 
				
			||||||
 | 
					|        create_branch         |   分支   |        創建一個新分支        |
 | 
				
			||||||
 | 
					|        delete_branch         |   分支   |         刪除一個分支         |
 | 
				
			||||||
 | 
					|        list_branches         |   分支   |     列出倉庫中的所有分支     |
 | 
				
			||||||
 | 
					|        create_release        | 版本發布 |      創建一個新版本發布      |
 | 
				
			||||||
 | 
					|        delete_release        | 版本發布 |       刪除一個版本發布       |
 | 
				
			||||||
 | 
					|         get_release          | 版本發布 |       獲取一個版本發布       |
 | 
				
			||||||
 | 
					|      get_latest_release      | 版本發布 |      獲取最新的版本發布      |
 | 
				
			||||||
 | 
					|        list_releases         | 版本發布 |       列出所有版本發布       |
 | 
				
			||||||
 | 
					|          create_tag          |   標籤   |        創建一個新標籤        |
 | 
				
			||||||
 | 
					|          delete_tag          |   標籤   |         刪除一個標籤         |
 | 
				
			||||||
 | 
					|           get_tag            |   標籤   |         獲取一個標籤         |
 | 
				
			||||||
 | 
					|          list_tags           |   標籤   |         列出所有標籤         |
 | 
				
			||||||
 | 
					|      list_repo_commits       |   提交   |     列出倉庫中的所有提交     |
 | 
				
			||||||
 | 
					|       get_file_content       |   文件   |    獲取文件的內容和元數據    |
 | 
				
			||||||
 | 
					|        get_dir_content       |   文件   |      獲取目錄的內容列表      |
 | 
				
			||||||
 | 
					|         create_file          |   文件   |        創建一個新文件        |
 | 
				
			||||||
 | 
					|         update_file          |   文件   |         更新現有文件         |
 | 
				
			||||||
 | 
					|         delete_file          |   文件   |         刪除一個文件         |
 | 
				
			||||||
 | 
					|      get_issue_by_index      |   問題   |       根據索引獲取問題       |
 | 
				
			||||||
 | 
					|       list_repo_issues       |   問題   |     列出倉庫中的所有問題     |
 | 
				
			||||||
 | 
					|         create_issue         |   問題   |        創建一個新問題        |
 | 
				
			||||||
 | 
					|     create_issue_comment     |   問題   |       在問題上創建評論       |
 | 
				
			||||||
 | 
					|          edit_issue          |   問題   |         編輯一個問題         |
 | 
				
			||||||
 | 
					|      edit_issue_comment      |   問題   |      在問題上編輯評論         |
 | 
				
			||||||
 | 
					| get_issue_comments_by_index  |   问题   |     根據索引獲取問題的評論     |
 | 
				
			||||||
 | 
					|  get_pull_request_by_index   | 拉取請求 |     根據索引獲取拉取請求     |
 | 
				
			||||||
 | 
					|   list_repo_pull_requests    | 拉取請求 |   列出倉庫中的所有拉取請求   |
 | 
				
			||||||
 | 
					|     create_pull_request      | 拉取請求 |      創建一個新拉取請求      |
 | 
				
			||||||
 | 
					|         search_users         |   用戶   |           搜索用戶           |
 | 
				
			||||||
 | 
					|       search_org_teams       |   組織   |       搜索組織中的團隊       |
 | 
				
			||||||
 | 
					|         search_repos         |   倉庫   |           搜索倉庫           |
 | 
				
			||||||
 | 
					| get_gitea_mcp_server_version |   伺服器    |        獲取 Gitea MCP 伺服器的版本         |
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 🐛 調試
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					要啟用調試模式,請在使用 sse 模式運行 Gitea MCP 伺服器時添加 `-d` 旗標:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					```sh
 | 
				
			||||||
 | 
					./gitea-mcp -t sse [--port 8080] --token <your personal access token> -d
 | 
				
			||||||
 | 
					```
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					## 🛠 疑難排解
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					如果您遇到任何問題,以下是一些常見的疑難排解步驟:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					1. **檢查您的 PATH**: 確保 `gitea-mcp` 二進制文件位於系統 PATH 中包含的目錄中。
 | 
				
			||||||
 | 
					2. **驗證依賴項**: 確保您已安裝所有所需的依賴項,例如 `make` 和 `Golang`。
 | 
				
			||||||
 | 
					3. **檢查配置**: 仔細檢查您的 MCP 配置文件是否有任何錯誤或遺漏的信息。
 | 
				
			||||||
 | 
					4. **查看日誌**: 檢查日誌中是否有任何錯誤消息或警告,可以提供有關問題的更多信息。
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					享受通過聊天探索和管理您的 Gitea 倉庫的樂趣!
 | 
				
			||||||
							
								
								
									
										64
									
								
								cmd/cmd.go
									
									
									
									
									
								
							
							
						
						
									
										64
									
								
								cmd/cmd.go
									
									
									
									
									
								
							@@ -11,38 +11,35 @@ import (
 | 
				
			|||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
	transport string
 | 
					 | 
				
			||||||
	host  string
 | 
						host  string
 | 
				
			||||||
	port  int
 | 
						port  int
 | 
				
			||||||
	token string
 | 
						token string
 | 
				
			||||||
 | 
					 | 
				
			||||||
	debug bool
 | 
					 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func init() {
 | 
					func init() {
 | 
				
			||||||
	flag.StringVar(
 | 
						flag.StringVar(
 | 
				
			||||||
		&transport,
 | 
							&flagPkg.Mode,
 | 
				
			||||||
		"t",
 | 
							"t",
 | 
				
			||||||
		"stdio",
 | 
							"stdio",
 | 
				
			||||||
		"Transport type (stdio or sse)",
 | 
							"Transport type (stdio, sse or http)",
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
	flag.StringVar(
 | 
						flag.StringVar(
 | 
				
			||||||
		&transport,
 | 
							&flagPkg.Mode,
 | 
				
			||||||
		"transport",
 | 
							"transport",
 | 
				
			||||||
		"stdio",
 | 
							"stdio",
 | 
				
			||||||
		"Transport type (stdio or sse)",
 | 
							"Transport type (stdio, sse or http)",
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
	flag.StringVar(
 | 
						flag.StringVar(
 | 
				
			||||||
		&host,
 | 
							&host,
 | 
				
			||||||
		"host",
 | 
							"host",
 | 
				
			||||||
		"https://gitea.com",
 | 
							os.Getenv("GITEA_HOST"),
 | 
				
			||||||
		"Gitea host",
 | 
							"Gitea host",
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
	flag.IntVar(
 | 
						flag.IntVar(
 | 
				
			||||||
		&port,
 | 
							&port,
 | 
				
			||||||
		"port",
 | 
							"port",
 | 
				
			||||||
		8080,
 | 
							8080,
 | 
				
			||||||
		"sse port",
 | 
							"see or http port",
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
	flag.StringVar(
 | 
						flag.StringVar(
 | 
				
			||||||
		&token,
 | 
							&token,
 | 
				
			||||||
@@ -51,24 +48,27 @@ func init() {
 | 
				
			|||||||
		"Your personal access token",
 | 
							"Your personal access token",
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
	flag.BoolVar(
 | 
						flag.BoolVar(
 | 
				
			||||||
		&debug,
 | 
							&flagPkg.ReadOnly,
 | 
				
			||||||
		"d",
 | 
							"read-only",
 | 
				
			||||||
		true,
 | 
							false,
 | 
				
			||||||
		"debug mode",
 | 
							"Read-only mode",
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
	flag.BoolVar(
 | 
						flag.BoolVar(
 | 
				
			||||||
		&debug,
 | 
							&flagPkg.Debug,
 | 
				
			||||||
		"debug",
 | 
							"d",
 | 
				
			||||||
		true,
 | 
							false,
 | 
				
			||||||
		"debug mode",
 | 
							"debug mode (If -d flag is provided, debug mode will be enabled by default)",
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
						flag.BoolVar(
 | 
				
			||||||
 | 
							&flagPkg.Insecure,
 | 
				
			||||||
 | 
							"insecure",
 | 
				
			||||||
 | 
							false,
 | 
				
			||||||
 | 
							"ignore TLS certificate errors",
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	flag.Parse()
 | 
						flag.Parse()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	flagPkg.Host = host
 | 
						flagPkg.Host = host
 | 
				
			||||||
	if flagPkg.Host == "" {
 | 
					 | 
				
			||||||
		flagPkg.Host = os.Getenv("GITEA_HOST")
 | 
					 | 
				
			||||||
	}
 | 
					 | 
				
			||||||
	if flagPkg.Host == "" {
 | 
						if flagPkg.Host == "" {
 | 
				
			||||||
		flagPkg.Host = "https://gitea.com"
 | 
							flagPkg.Host = "https://gitea.com"
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -80,19 +80,27 @@ func init() {
 | 
				
			|||||||
		flagPkg.Token = os.Getenv("GITEA_ACCESS_TOKEN")
 | 
							flagPkg.Token = os.Getenv("GITEA_ACCESS_TOKEN")
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	flagPkg.Mode = transport
 | 
						if os.Getenv("MCP_MODE") != "" {
 | 
				
			||||||
 | 
							flagPkg.Mode = os.Getenv("MCP_MODE")
 | 
				
			||||||
	if debug {
 | 
					 | 
				
			||||||
		flagPkg.Debug = debug
 | 
					 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	if !debug {
 | 
					
 | 
				
			||||||
		flagPkg.Debug = os.Getenv("GITEA_DEBUG") == "true"
 | 
						if os.Getenv("GITEA_READONLY") == "true" {
 | 
				
			||||||
 | 
							flagPkg.ReadOnly = true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						if os.Getenv("GITEA_DEBUG") == "true" {
 | 
				
			||||||
 | 
							flagPkg.Debug = true
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Set insecure mode based on environment variable
 | 
				
			||||||
 | 
						if os.Getenv("GITEA_INSECURE") == "true" {
 | 
				
			||||||
 | 
							flagPkg.Insecure = true
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func Execute(version string) {
 | 
					func Execute() {
 | 
				
			||||||
	defer log.Default().Sync()
 | 
						defer log.Default().Sync()
 | 
				
			||||||
	if err := operation.Run(transport, version); err != nil {
 | 
						if err := operation.Run(); err != nil {
 | 
				
			||||||
		if err == context.Canceled {
 | 
							if err == context.Canceled {
 | 
				
			||||||
			log.Info("Server shutdown due to context cancellation")
 | 
								log.Info("Server shutdown due to context cancellation")
 | 
				
			||||||
			return
 | 
								return
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										15
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										15
									
								
								go.mod
									
									
									
									
									
								
							@@ -4,19 +4,26 @@ go 1.24.0
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
	code.gitea.io/sdk/gitea v0.21.0
 | 
						code.gitea.io/sdk/gitea v0.21.0
 | 
				
			||||||
	github.com/mark3labs/mcp-go v0.18.0
 | 
						github.com/mark3labs/mcp-go v0.36.0
 | 
				
			||||||
	go.uber.org/zap v1.27.0
 | 
						go.uber.org/zap v1.27.0
 | 
				
			||||||
	gopkg.in/natefinch/lumberjack.v2 v2.2.1
 | 
						gopkg.in/natefinch/lumberjack.v2 v2.2.1
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require (
 | 
					require (
 | 
				
			||||||
	github.com/42wim/httpsig v1.2.2 // indirect
 | 
						github.com/42wim/httpsig v1.2.3 // indirect
 | 
				
			||||||
 | 
						github.com/bahlo/generic-list-go v0.2.0 // indirect
 | 
				
			||||||
 | 
						github.com/buger/jsonparser v1.1.1 // indirect
 | 
				
			||||||
	github.com/davidmz/go-pageant v1.0.2 // indirect
 | 
						github.com/davidmz/go-pageant v1.0.2 // indirect
 | 
				
			||||||
	github.com/go-fed/httpsig v1.1.0 // indirect
 | 
						github.com/go-fed/httpsig v1.1.0 // indirect
 | 
				
			||||||
	github.com/google/uuid v1.6.0 // indirect
 | 
						github.com/google/uuid v1.6.0 // indirect
 | 
				
			||||||
	github.com/hashicorp/go-version v1.7.0 // indirect
 | 
						github.com/hashicorp/go-version v1.7.0 // indirect
 | 
				
			||||||
 | 
						github.com/invopop/jsonschema v0.13.0 // indirect
 | 
				
			||||||
 | 
						github.com/mailru/easyjson v0.7.7 // indirect
 | 
				
			||||||
 | 
						github.com/spf13/cast v1.9.2 // indirect
 | 
				
			||||||
 | 
						github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
 | 
				
			||||||
	github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
 | 
						github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
 | 
				
			||||||
	go.uber.org/multierr v1.11.0 // indirect
 | 
						go.uber.org/multierr v1.11.0 // indirect
 | 
				
			||||||
	golang.org/x/crypto v0.36.0 // indirect
 | 
						golang.org/x/crypto v0.40.0 // indirect
 | 
				
			||||||
	golang.org/x/sys v0.32.0 // indirect
 | 
						golang.org/x/sys v0.34.0 // indirect
 | 
				
			||||||
 | 
						gopkg.in/yaml.v3 v3.0.1 // indirect
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										45
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										45
									
								
								go.sum
									
									
									
									
									
								
							@@ -1,23 +1,46 @@
 | 
				
			|||||||
code.gitea.io/sdk/gitea v0.21.0 h1:69n6oz6kEVHRo1+APQQyizkhrZrLsTLXey9142pfkD4=
 | 
					code.gitea.io/sdk/gitea v0.21.0 h1:69n6oz6kEVHRo1+APQQyizkhrZrLsTLXey9142pfkD4=
 | 
				
			||||||
code.gitea.io/sdk/gitea v0.21.0/go.mod h1:tnBjVhuKJCn8ibdyyhvUyxrR1Ca2KHEoTWoukNhXQPA=
 | 
					code.gitea.io/sdk/gitea v0.21.0/go.mod h1:tnBjVhuKJCn8ibdyyhvUyxrR1Ca2KHEoTWoukNhXQPA=
 | 
				
			||||||
github.com/42wim/httpsig v1.2.2 h1:ofAYoHUNs/MJOLqQ8hIxeyz2QxOz8qdSVvp3PX/oPgA=
 | 
					github.com/42wim/httpsig v1.2.3 h1:xb0YyWhkYj57SPtfSttIobJUPJZB9as1nsfo7KWVcEs=
 | 
				
			||||||
github.com/42wim/httpsig v1.2.2/go.mod h1:P/UYo7ytNBFwc+dg35IubuAUIs8zj5zzFIgUCEl55WY=
 | 
					github.com/42wim/httpsig v1.2.3/go.mod h1:nZq9OlYKDrUBhptd77IHx4/sZZD+IxTBADvAPI9G/EM=
 | 
				
			||||||
 | 
					github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
 | 
				
			||||||
 | 
					github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
 | 
				
			||||||
 | 
					github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
 | 
				
			||||||
 | 
					github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0=
 | 
				
			||||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
					github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
 | 
				
			||||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
					github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
 | 
				
			||||||
github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
 | 
					github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
 | 
				
			||||||
github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
 | 
					github.com/davidmz/go-pageant v1.0.2/go.mod h1:P2EDDnMqIwG5Rrp05dTRITj9z2zpGcD9efWSkTNKLIE=
 | 
				
			||||||
 | 
					github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
 | 
				
			||||||
 | 
					github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
 | 
				
			||||||
github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
 | 
					github.com/go-fed/httpsig v1.1.0 h1:9M+hb0jkEICD8/cAiNqEB66R87tTINszBRTjwjQzWcI=
 | 
				
			||||||
github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
 | 
					github.com/go-fed/httpsig v1.1.0/go.mod h1:RCMrTZvN1bJYtofsG4rd5NaO5obxQ5xBkdiS7xsT7bM=
 | 
				
			||||||
 | 
					github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
 | 
				
			||||||
 | 
					github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
 | 
				
			||||||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 | 
					github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
 | 
				
			||||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
					github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
 | 
				
			||||||
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
 | 
					github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
 | 
				
			||||||
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 | 
					github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 | 
				
			||||||
github.com/mark3labs/mcp-go v0.18.0 h1:YuhgIVjNlTG2ZOwmrkORWyPTp0dz1opPEqvsPtySXao=
 | 
					github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
 | 
				
			||||||
github.com/mark3labs/mcp-go v0.18.0/go.mod h1:KmJndYv7GIgcPVwEKJjNcbhVQ+hJGJhrCCB/9xITzpE=
 | 
					github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
 | 
				
			||||||
 | 
					github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 | 
				
			||||||
 | 
					github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 | 
				
			||||||
 | 
					github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 | 
				
			||||||
 | 
					github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 | 
				
			||||||
 | 
					github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 | 
				
			||||||
 | 
					github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
 | 
				
			||||||
 | 
					github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 | 
				
			||||||
 | 
					github.com/mark3labs/mcp-go v0.36.0 h1:rIZaijrRYPeSbJG8/qNDe0hWlGrCJ7FWHNMz2SQpTis=
 | 
				
			||||||
 | 
					github.com/mark3labs/mcp-go v0.36.0/go.mod h1:T7tUa2jO6MavG+3P25Oy/jR7iCeJPHImCZHRymCn39g=
 | 
				
			||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
					github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
				
			||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
					github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
 | 
				
			||||||
 | 
					github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
 | 
				
			||||||
 | 
					github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
 | 
				
			||||||
 | 
					github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
 | 
				
			||||||
 | 
					github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
 | 
				
			||||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
 | 
					github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
 | 
				
			||||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 | 
					github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
 | 
				
			||||||
 | 
					github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
 | 
				
			||||||
 | 
					github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
 | 
				
			||||||
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
 | 
					github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
 | 
				
			||||||
github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
 | 
					github.com/yosida95/uritemplate/v3 v3.0.2/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
 | 
				
			||||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
 | 
					go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
 | 
				
			||||||
@@ -29,21 +52,23 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
 | 
				
			|||||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 | 
					golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 | 
				
			||||||
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 | 
					golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
 | 
				
			||||||
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
 | 
					golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
 | 
				
			||||||
golang.org/x/crypto v0.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
 | 
					golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
 | 
				
			||||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
 | 
					golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
 | 
				
			||||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
					golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
				
			||||||
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
					golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
					golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
					golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
				
			||||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
 | 
					golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
 | 
				
			||||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 | 
					golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 | 
				
			||||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
					golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
				
			||||||
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
 | 
					golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
 | 
				
			||||||
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
 | 
					golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
 | 
				
			||||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
					golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
				
			||||||
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
					golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
 | 
				
			||||||
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
					golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
 | 
				
			||||||
 | 
					gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
 | 
				
			||||||
 | 
					gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
 | 
				
			||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
 | 
					gopkg.in/natefinch/lumberjack.v2 v2.2.1 h1:bBRl1b0OH9s/DuPhuXpNl+VtCaJXFZ5/uEFST95x9zc=
 | 
				
			||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
 | 
					gopkg.in/natefinch/lumberjack.v2 v2.2.1/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
 | 
				
			||||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 | 
					gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										9
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								main.go
									
									
									
									
									
								
							@@ -2,12 +2,17 @@ package main
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"gitea.com/gitea/gitea-mcp/cmd"
 | 
						"gitea.com/gitea/gitea-mcp/cmd"
 | 
				
			||||||
 | 
						"gitea.com/gitea/gitea-mcp/pkg/flag"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
	Version = "dev"
 | 
						Version = "dev"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func main() {
 | 
					func init() {
 | 
				
			||||||
	cmd.Execute(Version)
 | 
						flag.Version = Version
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func main() {
 | 
				
			||||||
 | 
						cmd.Execute()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -6,18 +6,25 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	"gitea.com/gitea/gitea-mcp/pkg/gitea"
 | 
						"gitea.com/gitea/gitea-mcp/pkg/gitea"
 | 
				
			||||||
	"gitea.com/gitea/gitea-mcp/pkg/log"
 | 
						"gitea.com/gitea/gitea-mcp/pkg/log"
 | 
				
			||||||
 | 
						"gitea.com/gitea/gitea-mcp/pkg/ptr"
 | 
				
			||||||
	"gitea.com/gitea/gitea-mcp/pkg/to"
 | 
						"gitea.com/gitea/gitea-mcp/pkg/to"
 | 
				
			||||||
 | 
						"gitea.com/gitea/gitea-mcp/pkg/tool"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	gitea_sdk "code.gitea.io/sdk/gitea"
 | 
						gitea_sdk "code.gitea.io/sdk/gitea"
 | 
				
			||||||
	"github.com/mark3labs/mcp-go/mcp"
 | 
						"github.com/mark3labs/mcp-go/mcp"
 | 
				
			||||||
	"github.com/mark3labs/mcp-go/server"
 | 
						"github.com/mark3labs/mcp-go/server"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var Tool = tool.New()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	GetIssueByIndexToolName         = "get_issue_by_index"
 | 
						GetIssueByIndexToolName         = "get_issue_by_index"
 | 
				
			||||||
	ListRepoIssuesToolName          = "list_repo_issues"
 | 
						ListRepoIssuesToolName          = "list_repo_issues"
 | 
				
			||||||
	CreateIssueToolName             = "create_issue"
 | 
						CreateIssueToolName             = "create_issue"
 | 
				
			||||||
	CreateIssueCommentToolName      = "create_issue_comment"
 | 
						CreateIssueCommentToolName      = "create_issue_comment"
 | 
				
			||||||
 | 
						EditIssueToolName               = "edit_issue"
 | 
				
			||||||
 | 
						EditIssueCommentToolName        = "edit_issue_comment"
 | 
				
			||||||
 | 
						GetIssueCommentsByIndexToolName = "get_issue_comments_by_index"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
@@ -47,6 +54,7 @@ var (
 | 
				
			|||||||
		mcp.WithString("title", mcp.Required(), mcp.Description("issue title")),
 | 
							mcp.WithString("title", mcp.Required(), mcp.Description("issue title")),
 | 
				
			||||||
		mcp.WithString("body", mcp.Required(), mcp.Description("issue body")),
 | 
							mcp.WithString("body", mcp.Required(), mcp.Description("issue body")),
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	CreateIssueCommentTool = mcp.NewTool(
 | 
						CreateIssueCommentTool = mcp.NewTool(
 | 
				
			||||||
		CreateIssueCommentToolName,
 | 
							CreateIssueCommentToolName,
 | 
				
			||||||
		mcp.WithDescription("create issue comment"),
 | 
							mcp.WithDescription("create issue comment"),
 | 
				
			||||||
@@ -55,30 +63,88 @@ var (
 | 
				
			|||||||
		mcp.WithNumber("index", mcp.Required(), mcp.Description("repository issue index")),
 | 
							mcp.WithNumber("index", mcp.Required(), mcp.Description("repository issue index")),
 | 
				
			||||||
		mcp.WithString("body", mcp.Required(), mcp.Description("issue comment body")),
 | 
							mcp.WithString("body", mcp.Required(), mcp.Description("issue comment body")),
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						EditIssueTool = mcp.NewTool(
 | 
				
			||||||
 | 
							EditIssueToolName,
 | 
				
			||||||
 | 
							mcp.WithDescription("edit issue"),
 | 
				
			||||||
 | 
							mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
				
			||||||
 | 
							mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
				
			||||||
 | 
							mcp.WithNumber("index", mcp.Required(), mcp.Description("repository issue index")),
 | 
				
			||||||
 | 
							mcp.WithString("title", mcp.Description("issue title"), mcp.DefaultString("")),
 | 
				
			||||||
 | 
							mcp.WithString("body", mcp.Description("issue body content")),
 | 
				
			||||||
 | 
							mcp.WithArray("assignees", mcp.Description("usernames to assign to this issue"), mcp.Items(map[string]interface{}{"type": "string"})),
 | 
				
			||||||
 | 
							mcp.WithNumber("milestone", mcp.Description("milestone number")),
 | 
				
			||||||
 | 
							mcp.WithString("state", mcp.Description("issue state, one of open, closed, all")),
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func RegisterTool(s *server.MCPServer) {
 | 
						EditIssueCommentTool = mcp.NewTool(
 | 
				
			||||||
	s.AddTool(GetIssueByIndexTool, GetIssueByIndexFn)
 | 
							EditIssueCommentToolName,
 | 
				
			||||||
	s.AddTool(ListRepoIssuesTool, ListRepoIssuesFn)
 | 
							mcp.WithDescription("edit issue comment"),
 | 
				
			||||||
	s.AddTool(CreateIssueTool, CreateIssueFn)
 | 
							mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
				
			||||||
	s.AddTool(CreateIssueCommentTool, CreateIssueCommentFn)
 | 
							mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
				
			||||||
 | 
							mcp.WithNumber("commentID", mcp.Required(), mcp.Description("id of issue comment")),
 | 
				
			||||||
 | 
							mcp.WithString("body", mcp.Required(), mcp.Description("issue comment body")),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						GetIssueCommentsByIndexTool = mcp.NewTool(
 | 
				
			||||||
 | 
							GetIssueCommentsByIndexToolName,
 | 
				
			||||||
 | 
							mcp.WithDescription("get issue comment by index"),
 | 
				
			||||||
 | 
							mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
				
			||||||
 | 
							mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
				
			||||||
 | 
							mcp.WithNumber("index", mcp.Required(), mcp.Description("repository issue index")),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						Tool.RegisterRead(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    GetIssueByIndexTool,
 | 
				
			||||||
 | 
							Handler: GetIssueByIndexFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterRead(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    ListRepoIssuesTool,
 | 
				
			||||||
 | 
							Handler: ListRepoIssuesFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterWrite(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    CreateIssueTool,
 | 
				
			||||||
 | 
							Handler: CreateIssueFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterWrite(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    CreateIssueCommentTool,
 | 
				
			||||||
 | 
							Handler: CreateIssueCommentFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterWrite(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    EditIssueTool,
 | 
				
			||||||
 | 
							Handler: EditIssueFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterWrite(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    EditIssueCommentTool,
 | 
				
			||||||
 | 
							Handler: EditIssueCommentFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterRead(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    GetIssueCommentsByIndexTool,
 | 
				
			||||||
 | 
							Handler: GetIssueCommentsByIndexFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetIssueByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
					func GetIssueByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
	log.Debugf("Called GetIssueByIndexFn")
 | 
						log.Debugf("Called GetIssueByIndexFn")
 | 
				
			||||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
							return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
							return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	index, ok := req.Params.Arguments["index"].(float64)
 | 
						index, ok := req.GetArguments()["index"].(float64)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("index is required"))
 | 
							return to.ErrorResult(fmt.Errorf("index is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	issue, _, err := gitea.Client().GetIssue(owner, repo, int64(index))
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						issue, _, err := client.GetIssue(owner, repo, int64(index))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("get %v/%v/issue/%v err: %v", owner, repo, int64(index), err))
 | 
							return to.ErrorResult(fmt.Errorf("get %v/%v/issue/%v err: %v", owner, repo, int64(index), err))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -88,23 +154,23 @@ func GetIssueByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func ListRepoIssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
					func ListRepoIssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
	log.Debugf("Called ListIssuesFn")
 | 
						log.Debugf("Called ListIssuesFn")
 | 
				
			||||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
							return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
							return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	state, ok := req.Params.Arguments["state"].(string)
 | 
						state, ok := req.GetArguments()["state"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		state = "all"
 | 
							state = "all"
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	page, ok := req.Params.Arguments["page"].(float64)
 | 
						page, ok := req.GetArguments()["page"].(float64)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		page = 1
 | 
							page = 1
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	pageSize, ok := req.Params.Arguments["pageSize"].(float64)
 | 
						pageSize, ok := req.GetArguments()["pageSize"].(float64)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		pageSize = 100
 | 
							pageSize = 100
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -115,7 +181,11 @@ func ListRepoIssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
 | 
				
			|||||||
			PageSize: int(pageSize),
 | 
								PageSize: int(pageSize),
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	issues, _, err := gitea.Client().ListRepoIssues(owner, repo, opt)
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						issues, _, err := client.ListRepoIssues(owner, repo, opt)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("get %v/%v/issues err: %v", owner, repo, err))
 | 
							return to.ErrorResult(fmt.Errorf("get %v/%v/issues err: %v", owner, repo, err))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -124,28 +194,32 @@ func ListRepoIssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func CreateIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
					func CreateIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
	log.Debugf("Called CreateIssueFn")
 | 
						log.Debugf("Called CreateIssueFn")
 | 
				
			||||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
							return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
							return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	title, ok := req.Params.Arguments["title"].(string)
 | 
						title, ok := req.GetArguments()["title"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("title is required"))
 | 
							return to.ErrorResult(fmt.Errorf("title is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	body, ok := req.Params.Arguments["body"].(string)
 | 
						body, ok := req.GetArguments()["body"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("body is required"))
 | 
							return to.ErrorResult(fmt.Errorf("body is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	issue, _, err := gitea.Client().CreateIssue(owner, repo, gitea_sdk.CreateIssueOption{
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						issue, _, err := client.CreateIssue(owner, repo, gitea_sdk.CreateIssueOption{
 | 
				
			||||||
		Title: title,
 | 
							Title: title,
 | 
				
			||||||
		Body:  body,
 | 
							Body:  body,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("create %v/%v/issue err", owner, repo))
 | 
							return to.ErrorResult(fmt.Errorf("create %v/%v/issue err: %v", owner, repo, err))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return to.TextResult(issue)
 | 
						return to.TextResult(issue)
 | 
				
			||||||
@@ -153,29 +227,143 @@ func CreateIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func CreateIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
					func CreateIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
	log.Debugf("Called CreateIssueCommentFn")
 | 
						log.Debugf("Called CreateIssueCommentFn")
 | 
				
			||||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
							return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
							return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	index, ok := req.Params.Arguments["index"].(float64)
 | 
						index, ok := req.GetArguments()["index"].(float64)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("index is required"))
 | 
							return to.ErrorResult(fmt.Errorf("index is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	body, ok := req.Params.Arguments["body"].(string)
 | 
						body, ok := req.GetArguments()["body"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("body is required"))
 | 
							return to.ErrorResult(fmt.Errorf("body is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	opt := gitea_sdk.CreateIssueCommentOption{
 | 
						opt := gitea_sdk.CreateIssueCommentOption{
 | 
				
			||||||
		Body: body,
 | 
							Body: body,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	issueComment, _, err := gitea.Client().CreateIssueComment(owner, repo, int64(index), opt)
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("create %v/%v/issue/%v/comment err", owner, repo, int64(index)))
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						issueComment, _, err := client.CreateIssueComment(owner, repo, int64(index), opt)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("create %v/%v/issue/%v/comment err: %v", owner, repo, int64(index), err))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return to.TextResult(issueComment)
 | 
						return to.TextResult(issueComment)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func EditIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
 | 
						log.Debugf("Called EditIssueFn")
 | 
				
			||||||
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						index, ok := req.GetArguments()["index"].(float64)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("index is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						opt := gitea_sdk.EditIssueOption{}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						title, ok := req.GetArguments()["title"].(string)
 | 
				
			||||||
 | 
						if ok {
 | 
				
			||||||
 | 
							opt.Title = title
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						body, ok := req.GetArguments()["body"].(string)
 | 
				
			||||||
 | 
						if ok {
 | 
				
			||||||
 | 
							opt.Body = ptr.To(body)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						assignees, ok := req.GetArguments()["assignees"].([]string)
 | 
				
			||||||
 | 
						if ok {
 | 
				
			||||||
 | 
							opt.Assignees = assignees
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						milestone, ok := req.GetArguments()["milestone"].(float64)
 | 
				
			||||||
 | 
						if ok {
 | 
				
			||||||
 | 
							opt.Milestone = ptr.To(int64(milestone))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						state, ok := req.GetArguments()["state"].(string)
 | 
				
			||||||
 | 
						if ok {
 | 
				
			||||||
 | 
							opt.State = ptr.To(gitea_sdk.StateType(state))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						issue, _, err := client.EditIssue(owner, repo, int64(index), opt)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("edit %v/%v/issue/%v err: %v", owner, repo, int64(index), err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return to.TextResult(issue)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func EditIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
 | 
						log.Debugf("Called EditIssueCommentFn")
 | 
				
			||||||
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						commentID, ok := req.GetArguments()["commentID"].(float64)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("comment ID is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						body, ok := req.GetArguments()["body"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("body is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						opt := gitea_sdk.EditIssueCommentOption{
 | 
				
			||||||
 | 
							Body: body,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						issueComment, _, err := client.EditIssueComment(owner, repo, int64(commentID), opt)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("edit %v/%v/issues/comments/%v err: %v", owner, repo, int64(commentID), err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return to.TextResult(issueComment)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetIssueCommentsByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
 | 
						log.Debugf("Called GetIssueCommentsByIndexFn")
 | 
				
			||||||
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						index, ok := req.GetArguments()["index"].(float64)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("index is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						opt := gitea_sdk.ListIssueCommentOptions{}
 | 
				
			||||||
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						issue, _, err := client.ListIssueComments(owner, repo, int64(index), opt)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get %v/%v/issues/%v/comments err: %v", owner, repo, int64(index), err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return to.TextResult(issue)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										454
									
								
								operation/label/label.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										454
									
								
								operation/label/label.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,454 @@
 | 
				
			|||||||
 | 
					package label
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"gitea.com/gitea/gitea-mcp/pkg/gitea"
 | 
				
			||||||
 | 
						"gitea.com/gitea/gitea-mcp/pkg/log"
 | 
				
			||||||
 | 
						"gitea.com/gitea/gitea-mcp/pkg/ptr"
 | 
				
			||||||
 | 
						"gitea.com/gitea/gitea-mcp/pkg/to"
 | 
				
			||||||
 | 
						"gitea.com/gitea/gitea-mcp/pkg/tool"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gitea_sdk "code.gitea.io/sdk/gitea"
 | 
				
			||||||
 | 
						"github.com/mark3labs/mcp-go/mcp"
 | 
				
			||||||
 | 
						"github.com/mark3labs/mcp-go/server"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var Tool = tool.New()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						ListRepoLabelsToolName     = "list_repo_labels"
 | 
				
			||||||
 | 
						GetRepoLabelToolName       = "get_repo_label"
 | 
				
			||||||
 | 
						CreateRepoLabelToolName    = "create_repo_label"
 | 
				
			||||||
 | 
						EditRepoLabelToolName      = "edit_repo_label"
 | 
				
			||||||
 | 
						DeleteRepoLabelToolName    = "delete_repo_label"
 | 
				
			||||||
 | 
						AddIssueLabelsToolName     = "add_issue_labels"
 | 
				
			||||||
 | 
						ReplaceIssueLabelsToolName = "replace_issue_labels"
 | 
				
			||||||
 | 
						ClearIssueLabelsToolName   = "clear_issue_labels"
 | 
				
			||||||
 | 
						RemoveIssueLabelToolName   = "remove_issue_label"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						ListRepoLabelsTool = mcp.NewTool(
 | 
				
			||||||
 | 
							ListRepoLabelsToolName,
 | 
				
			||||||
 | 
							mcp.WithDescription("Lists all labels for a given repository"),
 | 
				
			||||||
 | 
							mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
				
			||||||
 | 
							mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
				
			||||||
 | 
							mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
 | 
				
			||||||
 | 
							mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						GetRepoLabelTool = mcp.NewTool(
 | 
				
			||||||
 | 
							GetRepoLabelToolName,
 | 
				
			||||||
 | 
							mcp.WithDescription("Gets a single label by its ID for a repository"),
 | 
				
			||||||
 | 
							mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
				
			||||||
 | 
							mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
				
			||||||
 | 
							mcp.WithNumber("id", mcp.Required(), mcp.Description("label ID")),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						CreateRepoLabelTool = mcp.NewTool(
 | 
				
			||||||
 | 
							CreateRepoLabelToolName,
 | 
				
			||||||
 | 
							mcp.WithDescription("Creates a new label for a repository"),
 | 
				
			||||||
 | 
							mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
				
			||||||
 | 
							mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
				
			||||||
 | 
							mcp.WithString("name", mcp.Required(), mcp.Description("label name")),
 | 
				
			||||||
 | 
							mcp.WithString("color", mcp.Required(), mcp.Description("label color (hex code, e.g., #RRGGBB)")),
 | 
				
			||||||
 | 
							mcp.WithString("description", mcp.Description("label description")),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						EditRepoLabelTool = mcp.NewTool(
 | 
				
			||||||
 | 
							EditRepoLabelToolName,
 | 
				
			||||||
 | 
							mcp.WithDescription("Edits an existing label in a repository"),
 | 
				
			||||||
 | 
							mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
				
			||||||
 | 
							mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
				
			||||||
 | 
							mcp.WithNumber("id", mcp.Required(), mcp.Description("label ID")),
 | 
				
			||||||
 | 
							mcp.WithString("name", mcp.Description("new label name")),
 | 
				
			||||||
 | 
							mcp.WithString("color", mcp.Description("new label color (hex code, e.g., #RRGGBB)")),
 | 
				
			||||||
 | 
							mcp.WithString("description", mcp.Description("new label description")),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						DeleteRepoLabelTool = mcp.NewTool(
 | 
				
			||||||
 | 
							DeleteRepoLabelToolName,
 | 
				
			||||||
 | 
							mcp.WithDescription("Deletes a label from a repository"),
 | 
				
			||||||
 | 
							mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
				
			||||||
 | 
							mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
				
			||||||
 | 
							mcp.WithNumber("id", mcp.Required(), mcp.Description("label ID")),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						AddIssueLabelsTool = mcp.NewTool(
 | 
				
			||||||
 | 
							AddIssueLabelsToolName,
 | 
				
			||||||
 | 
							mcp.WithDescription("Adds one or more labels to an issue"),
 | 
				
			||||||
 | 
							mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
				
			||||||
 | 
							mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
				
			||||||
 | 
							mcp.WithNumber("index", mcp.Required(), mcp.Description("issue index")),
 | 
				
			||||||
 | 
							mcp.WithArray("labels", mcp.Required(), mcp.Description("array of label IDs to add"), mcp.Items(map[string]interface{}{"type": "number"})),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ReplaceIssueLabelsTool = mcp.NewTool(
 | 
				
			||||||
 | 
							ReplaceIssueLabelsToolName,
 | 
				
			||||||
 | 
							mcp.WithDescription("Replaces all labels on an issue"),
 | 
				
			||||||
 | 
							mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
				
			||||||
 | 
							mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
				
			||||||
 | 
							mcp.WithNumber("index", mcp.Required(), mcp.Description("issue index")),
 | 
				
			||||||
 | 
							mcp.WithArray("labels", mcp.Required(), mcp.Description("array of label IDs to replace with"), mcp.Items(map[string]interface{}{"type": "number"})),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ClearIssueLabelsTool = mcp.NewTool(
 | 
				
			||||||
 | 
							ClearIssueLabelsToolName,
 | 
				
			||||||
 | 
							mcp.WithDescription("Removes all labels from an issue"),
 | 
				
			||||||
 | 
							mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
				
			||||||
 | 
							mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
				
			||||||
 | 
							mcp.WithNumber("index", mcp.Required(), mcp.Description("issue index")),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						RemoveIssueLabelTool = mcp.NewTool(
 | 
				
			||||||
 | 
							RemoveIssueLabelToolName,
 | 
				
			||||||
 | 
							mcp.WithDescription("Removes a single label from an issue"),
 | 
				
			||||||
 | 
							mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
				
			||||||
 | 
							mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
				
			||||||
 | 
							mcp.WithNumber("index", mcp.Required(), mcp.Description("issue index")),
 | 
				
			||||||
 | 
							mcp.WithNumber("label_id", mcp.Required(), mcp.Description("label ID to remove")),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						Tool.RegisterRead(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    ListRepoLabelsTool,
 | 
				
			||||||
 | 
							Handler: ListRepoLabelsFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterRead(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    GetRepoLabelTool,
 | 
				
			||||||
 | 
							Handler: GetRepoLabelFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterWrite(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    CreateRepoLabelTool,
 | 
				
			||||||
 | 
							Handler: CreateRepoLabelFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterWrite(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    EditRepoLabelTool,
 | 
				
			||||||
 | 
							Handler: EditRepoLabelFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterWrite(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    DeleteRepoLabelTool,
 | 
				
			||||||
 | 
							Handler: DeleteRepoLabelFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterWrite(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    AddIssueLabelsTool,
 | 
				
			||||||
 | 
							Handler: AddIssueLabelsFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterWrite(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    ReplaceIssueLabelsTool,
 | 
				
			||||||
 | 
							Handler: ReplaceIssueLabelsFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterWrite(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    ClearIssueLabelsTool,
 | 
				
			||||||
 | 
							Handler: ClearIssueLabelsFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterWrite(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    RemoveIssueLabelTool,
 | 
				
			||||||
 | 
							Handler: RemoveIssueLabelFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ListRepoLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
 | 
						log.Debugf("Called ListRepoLabelsFn")
 | 
				
			||||||
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						page, ok := req.GetArguments()["page"].(float64)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							page = 1
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						pageSize, ok := req.GetArguments()["pageSize"].(float64)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							pageSize = 100
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						opt := gitea_sdk.ListLabelsOptions{
 | 
				
			||||||
 | 
							ListOptions: gitea_sdk.ListOptions{
 | 
				
			||||||
 | 
								Page:     int(page),
 | 
				
			||||||
 | 
								PageSize: int(pageSize),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						labels, _, err := client.ListRepoLabels(owner, repo, opt)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("list %v/%v/labels err: %v", owner, repo, err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return to.TextResult(labels)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
 | 
						log.Debugf("Called GetRepoLabelFn")
 | 
				
			||||||
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						id, ok := req.GetArguments()["id"].(float64)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("label ID is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						label, _, err := client.GetRepoLabel(owner, repo, int64(id))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get %v/%v/label/%v err: %v", owner, repo, int64(id), err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return to.TextResult(label)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func CreateRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
 | 
						log.Debugf("Called CreateRepoLabelFn")
 | 
				
			||||||
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						name, ok := req.GetArguments()["name"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("name is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						color, ok := req.GetArguments()["color"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("color is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						description, _ := req.GetArguments()["description"].(string) // Optional
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						opt := gitea_sdk.CreateLabelOption{
 | 
				
			||||||
 | 
							Name:        name,
 | 
				
			||||||
 | 
							Color:       color,
 | 
				
			||||||
 | 
							Description: description,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						label, _, err := client.CreateLabel(owner, repo, opt)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("create %v/%v/label err: %v", owner, repo, err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return to.TextResult(label)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func EditRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
 | 
						log.Debugf("Called EditRepoLabelFn")
 | 
				
			||||||
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						id, ok := req.GetArguments()["id"].(float64)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("label ID is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						opt := gitea_sdk.EditLabelOption{}
 | 
				
			||||||
 | 
						if name, ok := req.GetArguments()["name"].(string); ok {
 | 
				
			||||||
 | 
							opt.Name = ptr.To(name)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if color, ok := req.GetArguments()["color"].(string); ok {
 | 
				
			||||||
 | 
							opt.Color = ptr.To(color)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if description, ok := req.GetArguments()["description"].(string); ok {
 | 
				
			||||||
 | 
							opt.Description = ptr.To(description)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						label, _, err := client.EditLabel(owner, repo, int64(id), opt)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("edit %v/%v/label/%v err: %v", owner, repo, int64(id), err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return to.TextResult(label)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func DeleteRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
 | 
						log.Debugf("Called DeleteRepoLabelFn")
 | 
				
			||||||
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						id, ok := req.GetArguments()["id"].(float64)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("label ID is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						_, err = client.DeleteLabel(owner, repo, int64(id))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("delete %v/%v/label/%v err: %v", owner, repo, int64(id), err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return to.TextResult("Label deleted successfully")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func AddIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
 | 
						log.Debugf("Called AddIssueLabelsFn")
 | 
				
			||||||
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						index, ok := req.GetArguments()["index"].(float64)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("issue index is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						labelsRaw, ok := req.GetArguments()["labels"].([]interface{})
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("labels (array of IDs) is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var labels []int64
 | 
				
			||||||
 | 
						for _, l := range labelsRaw {
 | 
				
			||||||
 | 
							if labelID, ok := l.(float64); ok {
 | 
				
			||||||
 | 
								labels = append(labels, int64(labelID))
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								return to.ErrorResult(fmt.Errorf("invalid label ID in labels array"))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						opt := gitea_sdk.IssueLabelsOption{
 | 
				
			||||||
 | 
							Labels: labels,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						issueLabels, _, err := client.AddIssueLabels(owner, repo, int64(index), opt)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("add labels to %v/%v/issue/%v err: %v", owner, repo, int64(index), err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return to.TextResult(issueLabels)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ReplaceIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
 | 
						log.Debugf("Called ReplaceIssueLabelsFn")
 | 
				
			||||||
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						index, ok := req.GetArguments()["index"].(float64)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("issue index is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						labelsRaw, ok := req.GetArguments()["labels"].([]interface{})
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("labels (array of IDs) is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var labels []int64
 | 
				
			||||||
 | 
						for _, l := range labelsRaw {
 | 
				
			||||||
 | 
							if labelID, ok := l.(float64); ok {
 | 
				
			||||||
 | 
								labels = append(labels, int64(labelID))
 | 
				
			||||||
 | 
							} else {
 | 
				
			||||||
 | 
								return to.ErrorResult(fmt.Errorf("invalid label ID in labels array"))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						opt := gitea_sdk.IssueLabelsOption{
 | 
				
			||||||
 | 
							Labels: labels,
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						issueLabels, _, err := client.ReplaceIssueLabels(owner, repo, int64(index), opt)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("replace labels on %v/%v/issue/%v err: %v", owner, repo, int64(index), err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return to.TextResult(issueLabels)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ClearIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
 | 
						log.Debugf("Called ClearIssueLabelsFn")
 | 
				
			||||||
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						index, ok := req.GetArguments()["index"].(float64)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("issue index is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						_, err = client.ClearIssueLabels(owner, repo, int64(index))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("clear labels on %v/%v/issue/%v err: %v", owner, repo, int64(index), err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return to.TextResult("Labels cleared successfully")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func RemoveIssueLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
 | 
						log.Debugf("Called RemoveIssueLabelFn")
 | 
				
			||||||
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						index, ok := req.GetArguments()["index"].(float64)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("issue index is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						labelID, ok := req.GetArguments()["label_id"].(float64)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("label ID is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						_, err = client.DeleteIssueLabel(owner, repo, int64(index), int64(labelID))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("remove label %v from %v/%v/issue/%v err: %v", int64(labelID), owner, repo, int64(index), err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return to.TextResult("Label removed successfully")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -1,61 +1,99 @@
 | 
				
			|||||||
package operation
 | 
					package operation
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
						"net/http"
 | 
				
			||||||
 | 
						"strings"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"gitea.com/gitea/gitea-mcp/operation/issue"
 | 
						"gitea.com/gitea/gitea-mcp/operation/issue"
 | 
				
			||||||
 | 
						"gitea.com/gitea/gitea-mcp/operation/label"
 | 
				
			||||||
	"gitea.com/gitea/gitea-mcp/operation/pull"
 | 
						"gitea.com/gitea/gitea-mcp/operation/pull"
 | 
				
			||||||
	"gitea.com/gitea/gitea-mcp/operation/repo"
 | 
						"gitea.com/gitea/gitea-mcp/operation/repo"
 | 
				
			||||||
	"gitea.com/gitea/gitea-mcp/operation/search"
 | 
						"gitea.com/gitea/gitea-mcp/operation/search"
 | 
				
			||||||
	"gitea.com/gitea/gitea-mcp/operation/user"
 | 
						"gitea.com/gitea/gitea-mcp/operation/user"
 | 
				
			||||||
	"gitea.com/gitea/gitea-mcp/operation/version"
 | 
						"gitea.com/gitea/gitea-mcp/operation/version"
 | 
				
			||||||
 | 
						mcpContext "gitea.com/gitea/gitea-mcp/pkg/context"
 | 
				
			||||||
	"gitea.com/gitea/gitea-mcp/pkg/flag"
 | 
						"gitea.com/gitea/gitea-mcp/pkg/flag"
 | 
				
			||||||
	"gitea.com/gitea/gitea-mcp/pkg/log"
 | 
						"gitea.com/gitea/gitea-mcp/pkg/log"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/mark3labs/mcp-go/server"
 | 
						"github.com/mark3labs/mcp-go/server"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var mcpServer *server.MCPServer
 | 
				
			||||||
	mcpServer *server.MCPServer
 | 
					 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
func RegisterTool(s *server.MCPServer) {
 | 
					func RegisterTool(s *server.MCPServer) {
 | 
				
			||||||
	// User Tool
 | 
						// User Tool
 | 
				
			||||||
	user.RegisterTool(s)
 | 
						s.AddTools(user.Tool.Tools()...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Repo Tool
 | 
						// Repo Tool
 | 
				
			||||||
	repo.RegisterTool(s)
 | 
						s.AddTools(repo.Tool.Tools()...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Issue Tool
 | 
						// Issue Tool
 | 
				
			||||||
	issue.RegisterTool(s)
 | 
						s.AddTools(issue.Tool.Tools()...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Label Tool
 | 
				
			||||||
 | 
						s.AddTools(label.Tool.Tools()...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Pull Tool
 | 
						// Pull Tool
 | 
				
			||||||
	pull.RegisterTool(s)
 | 
						s.AddTools(pull.Tool.Tools()...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Search Tool
 | 
						// Search Tool
 | 
				
			||||||
	search.RegisterTool(s)
 | 
						s.AddTools(search.Tool.Tools()...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Version Tool
 | 
						// Version Tool
 | 
				
			||||||
	version.RegisterTool(s)
 | 
						s.AddTools(version.Tool.Tools()...)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						s.DeleteTools("")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func Run(transport, version string) error {
 | 
					func getContextWithToken(ctx context.Context, r *http.Request) context.Context {
 | 
				
			||||||
	flag.Version = version
 | 
						authHeader := r.Header.Get("Authorization")
 | 
				
			||||||
	mcpServer = newMCPServer(version)
 | 
						if authHeader == "" {
 | 
				
			||||||
 | 
							return ctx
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						parts := strings.Split(authHeader, " ")
 | 
				
			||||||
 | 
						if len(parts) != 2 || parts[0] != "Bearer" {
 | 
				
			||||||
 | 
							return ctx
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return context.WithValue(ctx, mcpContext.TokenContextKey, parts[1])
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func Run() error {
 | 
				
			||||||
 | 
						mcpServer = newMCPServer(flag.Version)
 | 
				
			||||||
	RegisterTool(mcpServer)
 | 
						RegisterTool(mcpServer)
 | 
				
			||||||
	switch transport {
 | 
						switch flag.Mode {
 | 
				
			||||||
	case "stdio":
 | 
						case "stdio":
 | 
				
			||||||
		if err := server.ServeStdio(mcpServer); err != nil {
 | 
							if err := server.ServeStdio(
 | 
				
			||||||
 | 
								mcpServer,
 | 
				
			||||||
 | 
							); err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
	case "sse":
 | 
						case "sse":
 | 
				
			||||||
		sseServer := server.NewSSEServer(mcpServer)
 | 
							sseServer := server.NewSSEServer(
 | 
				
			||||||
 | 
								mcpServer,
 | 
				
			||||||
 | 
								server.WithSSEContextFunc(getContextWithToken),
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
		log.Infof("Gitea MCP SSE server listening on :%d", flag.Port)
 | 
							log.Infof("Gitea MCP SSE server listening on :%d", flag.Port)
 | 
				
			||||||
		if err := sseServer.Start(fmt.Sprintf(":%d", flag.Port)); err != nil {
 | 
							if err := sseServer.Start(fmt.Sprintf(":%d", flag.Port)); err != nil {
 | 
				
			||||||
			return err
 | 
								return err
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
						case "http":
 | 
				
			||||||
 | 
							httpServer := server.NewStreamableHTTPServer(
 | 
				
			||||||
 | 
								mcpServer,
 | 
				
			||||||
 | 
								server.WithLogger(log.New()),
 | 
				
			||||||
 | 
								server.WithHeartbeatInterval(30*time.Second),
 | 
				
			||||||
 | 
								server.WithHTTPContextFunc(getContextWithToken),
 | 
				
			||||||
 | 
							)
 | 
				
			||||||
 | 
							log.Infof("Gitea MCP HTTP server listening on :%d", flag.Port)
 | 
				
			||||||
 | 
							if err := httpServer.Start(fmt.Sprintf(":%d", flag.Port)); err != nil {
 | 
				
			||||||
 | 
								return err
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	default:
 | 
						default:
 | 
				
			||||||
		return fmt.Errorf("invalid transport type: %s. Must be 'stdio' or 'sse'", transport)
 | 
							return fmt.Errorf("invalid transport type: %s. Must be 'stdio', 'sse' or 'http'", flag.Mode)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return nil
 | 
						return nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
@@ -64,6 +102,8 @@ func newMCPServer(version string) *server.MCPServer {
 | 
				
			|||||||
	return server.NewMCPServer(
 | 
						return server.NewMCPServer(
 | 
				
			||||||
		"Gitea MCP Server",
 | 
							"Gitea MCP Server",
 | 
				
			||||||
		version,
 | 
							version,
 | 
				
			||||||
 | 
							server.WithToolCapabilities(true),
 | 
				
			||||||
		server.WithLogging(),
 | 
							server.WithLogging(),
 | 
				
			||||||
 | 
							server.WithRecovery(),
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,12 +7,15 @@ import (
 | 
				
			|||||||
	"gitea.com/gitea/gitea-mcp/pkg/gitea"
 | 
						"gitea.com/gitea/gitea-mcp/pkg/gitea"
 | 
				
			||||||
	"gitea.com/gitea/gitea-mcp/pkg/log"
 | 
						"gitea.com/gitea/gitea-mcp/pkg/log"
 | 
				
			||||||
	"gitea.com/gitea/gitea-mcp/pkg/to"
 | 
						"gitea.com/gitea/gitea-mcp/pkg/to"
 | 
				
			||||||
 | 
						"gitea.com/gitea/gitea-mcp/pkg/tool"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	gitea_sdk "code.gitea.io/sdk/gitea"
 | 
						gitea_sdk "code.gitea.io/sdk/gitea"
 | 
				
			||||||
	"github.com/mark3labs/mcp-go/mcp"
 | 
						"github.com/mark3labs/mcp-go/mcp"
 | 
				
			||||||
	"github.com/mark3labs/mcp-go/server"
 | 
						"github.com/mark3labs/mcp-go/server"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var Tool = tool.New()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	GetPullRequestByIndexToolName = "get_pull_request_by_index"
 | 
						GetPullRequestByIndexToolName = "get_pull_request_by_index"
 | 
				
			||||||
	ListRepoPullRequestsToolName  = "list_repo_pull_requests"
 | 
						ListRepoPullRequestsToolName  = "list_repo_pull_requests"
 | 
				
			||||||
@@ -52,27 +55,40 @@ var (
 | 
				
			|||||||
	)
 | 
						)
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func RegisterTool(s *server.MCPServer) {
 | 
					func init() {
 | 
				
			||||||
	s.AddTool(GetPullRequestByIndexTool, GetPullRequestByIndexFn)
 | 
						Tool.RegisterRead(server.ServerTool{
 | 
				
			||||||
	s.AddTool(ListRepoPullRequestsTool, ListRepoPullRequestsFn)
 | 
							Tool:    GetPullRequestByIndexTool,
 | 
				
			||||||
	s.AddTool(CreatePullRequestTool, CreatePullRequestFn)
 | 
							Handler: GetPullRequestByIndexFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterRead(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    ListRepoPullRequestsTool,
 | 
				
			||||||
 | 
							Handler: ListRepoPullRequestsFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterWrite(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    CreatePullRequestTool,
 | 
				
			||||||
 | 
							Handler: CreatePullRequestFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetPullRequestByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
					func GetPullRequestByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
	log.Debugf("Called GetPullRequestByIndexFn")
 | 
						log.Debugf("Called GetPullRequestByIndexFn")
 | 
				
			||||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
							return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
							return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	index, ok := req.Params.Arguments["index"].(float64)
 | 
						index, ok := req.GetArguments()["index"].(float64)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("index is required"))
 | 
							return to.ErrorResult(fmt.Errorf("index is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	pr, _, err := gitea.Client().GetPullRequest(owner, repo, int64(index))
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						pr, _, err := client.GetPullRequest(owner, repo, int64(index))
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("get %v/%v/pr/%v err: %v", owner, repo, int64(index), err))
 | 
							return to.ErrorResult(fmt.Errorf("get %v/%v/pr/%v err: %v", owner, repo, int64(index), err))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -82,25 +98,25 @@ func GetPullRequestByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func ListRepoPullRequestsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
					func ListRepoPullRequestsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
	log.Debugf("Called ListRepoPullRequests")
 | 
						log.Debugf("Called ListRepoPullRequests")
 | 
				
			||||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
							return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
							return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	state, _ := req.Params.Arguments["state"].(string)
 | 
						state, _ := req.GetArguments()["state"].(string)
 | 
				
			||||||
	sort, ok := req.Params.Arguments["sort"].(string)
 | 
						sort, ok := req.GetArguments()["sort"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		sort = "recentupdate"
 | 
							sort = "recentupdate"
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	milestone, _ := req.Params.Arguments["milestone"].(float64)
 | 
						milestone, _ := req.GetArguments()["milestone"].(float64)
 | 
				
			||||||
	page, ok := req.Params.Arguments["page"].(float64)
 | 
						page, ok := req.GetArguments()["page"].(float64)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		page = 1
 | 
							page = 1
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	pageSize, ok := req.Params.Arguments["pageSize"].(float64)
 | 
						pageSize, ok := req.GetArguments()["pageSize"].(float64)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		pageSize = 100
 | 
							pageSize = 100
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -113,7 +129,11 @@ func ListRepoPullRequestsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.
 | 
				
			|||||||
			PageSize: int(pageSize),
 | 
								PageSize: int(pageSize),
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	pullRequests, _, err := gitea.Client().ListRepoPullRequests(owner, repo, opt)
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						pullRequests, _, err := client.ListRepoPullRequests(owner, repo, opt)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("list %v/%v/pull_requests err: %v", owner, repo, err))
 | 
							return to.ErrorResult(fmt.Errorf("list %v/%v/pull_requests err: %v", owner, repo, err))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -123,31 +143,35 @@ func ListRepoPullRequestsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func CreatePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
					func CreatePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
	log.Debugf("Called CreatePullRequestFn")
 | 
						log.Debugf("Called CreatePullRequestFn")
 | 
				
			||||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
							return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
							return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	title, ok := req.Params.Arguments["title"].(string)
 | 
						title, ok := req.GetArguments()["title"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("title is required"))
 | 
							return to.ErrorResult(fmt.Errorf("title is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	body, ok := req.Params.Arguments["body"].(string)
 | 
						body, ok := req.GetArguments()["body"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("body is required"))
 | 
							return to.ErrorResult(fmt.Errorf("body is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	head, ok := req.Params.Arguments["head"].(string)
 | 
						head, ok := req.GetArguments()["head"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("head is required"))
 | 
							return to.ErrorResult(fmt.Errorf("head is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	base, ok := req.Params.Arguments["base"].(string)
 | 
						base, ok := req.GetArguments()["base"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("base is required"))
 | 
							return to.ErrorResult(fmt.Errorf("base is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	pr, _, err := gitea.Client().CreatePullRequest(owner, repo, gitea_sdk.CreatePullRequestOption{
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						pr, _, err := client.CreatePullRequest(owner, repo, gitea_sdk.CreatePullRequestOption{
 | 
				
			||||||
		Title: title,
 | 
							Title: title,
 | 
				
			||||||
		Body:  body,
 | 
							Body:  body,
 | 
				
			||||||
		Head:  head,
 | 
							Head:  head,
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,6 +10,7 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	gitea_sdk "code.gitea.io/sdk/gitea"
 | 
						gitea_sdk "code.gitea.io/sdk/gitea"
 | 
				
			||||||
	"github.com/mark3labs/mcp-go/mcp"
 | 
						"github.com/mark3labs/mcp-go/mcp"
 | 
				
			||||||
 | 
						"github.com/mark3labs/mcp-go/server"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
@@ -44,23 +45,42 @@ var (
 | 
				
			|||||||
	)
 | 
						)
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						Tool.RegisterWrite(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    CreateBranchTool,
 | 
				
			||||||
 | 
							Handler: CreateBranchFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterWrite(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    DeleteBranchTool,
 | 
				
			||||||
 | 
							Handler: DeleteBranchFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterRead(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    ListBranchesTool,
 | 
				
			||||||
 | 
							Handler: ListBranchesFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func CreateBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
					func CreateBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
	log.Debugf("Called CreateBranchFn")
 | 
						log.Debugf("Called CreateBranchFn")
 | 
				
			||||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
							return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
							return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	branch, ok := req.Params.Arguments["branch"].(string)
 | 
						branch, ok := req.GetArguments()["branch"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("branch is required"))
 | 
							return to.ErrorResult(fmt.Errorf("branch is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	oldBranch, _ := req.Params.Arguments["old_branch"].(string)
 | 
						oldBranch, _ := req.GetArguments()["old_branch"].(string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, _, err := gitea.Client().CreateBranch(owner, repo, gitea_sdk.CreateBranchOption{
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						_, _, err = client.CreateBranch(owner, repo, gitea_sdk.CreateBranchOption{
 | 
				
			||||||
		BranchName:    branch,
 | 
							BranchName:    branch,
 | 
				
			||||||
		OldBranchName: oldBranch,
 | 
							OldBranchName: oldBranch,
 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
@@ -73,19 +93,23 @@ func CreateBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func DeleteBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
					func DeleteBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
	log.Debugf("Called DeleteBranchFn")
 | 
						log.Debugf("Called DeleteBranchFn")
 | 
				
			||||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
							return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
							return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	branch, ok := req.Params.Arguments["branch"].(string)
 | 
						branch, ok := req.GetArguments()["branch"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("branch is required"))
 | 
							return to.ErrorResult(fmt.Errorf("branch is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	_, _, err := gitea.Client().DeleteRepoBranch(owner, repo, branch)
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						_, _, err = client.DeleteRepoBranch(owner, repo, branch)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("delete branch error: %v", err))
 | 
							return to.ErrorResult(fmt.Errorf("delete branch error: %v", err))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -95,11 +119,11 @@ func DeleteBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func ListBranchesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
					func ListBranchesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
	log.Debugf("Called ListBranchesFn")
 | 
						log.Debugf("Called ListBranchesFn")
 | 
				
			||||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
							return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
							return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -109,7 +133,11 @@ func ListBranchesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
 | 
				
			|||||||
			PageSize: 100,
 | 
								PageSize: 100,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	branches, _, err := gitea.Client().ListRepoBranches(owner, repo, opt)
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						branches, _, err := client.ListRepoBranches(owner, repo, opt)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("list branches error: %v", err))
 | 
							return to.ErrorResult(fmt.Errorf("list branches error: %v", err))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -10,14 +10,14 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	gitea_sdk "code.gitea.io/sdk/gitea"
 | 
						gitea_sdk "code.gitea.io/sdk/gitea"
 | 
				
			||||||
	"github.com/mark3labs/mcp-go/mcp"
 | 
						"github.com/mark3labs/mcp-go/mcp"
 | 
				
			||||||
 | 
						"github.com/mark3labs/mcp-go/server"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	ListRepoCommitsToolName = "list_repo_commits"
 | 
						ListRepoCommitsToolName = "list_repo_commits"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var ListRepoCommitsTool = mcp.NewTool(
 | 
				
			||||||
	ListRepoCommitsTool = mcp.NewTool(
 | 
					 | 
				
			||||||
	ListRepoCommitsToolName,
 | 
						ListRepoCommitsToolName,
 | 
				
			||||||
	mcp.WithDescription("List repository commits"),
 | 
						mcp.WithDescription("List repository commits"),
 | 
				
			||||||
	mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
						mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
				
			||||||
@@ -27,28 +27,34 @@ var (
 | 
				
			|||||||
	mcp.WithNumber("page", mcp.Required(), mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)),
 | 
						mcp.WithNumber("page", mcp.Required(), mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)),
 | 
				
			||||||
	mcp.WithNumber("page_size", mcp.Required(), mcp.Description("page size"), mcp.DefaultNumber(50), mcp.Min(1)),
 | 
						mcp.WithNumber("page_size", mcp.Required(), mcp.Description("page size"), mcp.DefaultNumber(50), mcp.Min(1)),
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
)
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						Tool.RegisterRead(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    ListRepoCommitsTool,
 | 
				
			||||||
 | 
							Handler: ListRepoCommitsFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func ListRepoCommitsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
					func ListRepoCommitsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
	log.Debugf("Called ListRepoCommitsFn")
 | 
						log.Debugf("Called ListRepoCommitsFn")
 | 
				
			||||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
							return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
							return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	page, ok := req.Params.Arguments["page"].(float64)
 | 
						page, ok := req.GetArguments()["page"].(float64)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("page is required"))
 | 
							return to.ErrorResult(fmt.Errorf("page is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	pageSize, ok := req.Params.Arguments["page_size"].(float64)
 | 
						pageSize, ok := req.GetArguments()["page_size"].(float64)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("page_size is required"))
 | 
							return to.ErrorResult(fmt.Errorf("page_size is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	sha, _ := req.Params.Arguments["sha"].(string)
 | 
						sha, _ := req.GetArguments()["sha"].(string)
 | 
				
			||||||
	path, _ := req.Params.Arguments["path"].(string)
 | 
						path, _ := req.GetArguments()["path"].(string)
 | 
				
			||||||
	opt := gitea_sdk.ListCommitOptions{
 | 
						opt := gitea_sdk.ListCommitOptions{
 | 
				
			||||||
		ListOptions: gitea_sdk.ListOptions{
 | 
							ListOptions: gitea_sdk.ListOptions{
 | 
				
			||||||
			Page:     int(page),
 | 
								Page:     int(page),
 | 
				
			||||||
@@ -57,7 +63,11 @@ func ListRepoCommitsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
 | 
				
			|||||||
		SHA:  sha,
 | 
							SHA:  sha,
 | 
				
			||||||
		Path: path,
 | 
							Path: path,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	commits, _, err := gitea.Client().ListRepoCommits(owner, repo, opt)
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						commits, _, err := client.ListRepoCommits(owner, repo, opt)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("list repo commits err: %v", err))
 | 
							return to.ErrorResult(fmt.Errorf("list repo commits err: %v", err))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,8 +1,11 @@
 | 
				
			|||||||
package repo
 | 
					package repo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
 | 
						"bufio"
 | 
				
			||||||
 | 
						"bytes"
 | 
				
			||||||
	"context"
 | 
						"context"
 | 
				
			||||||
	"encoding/base64"
 | 
						"encoding/base64"
 | 
				
			||||||
 | 
						"encoding/json"
 | 
				
			||||||
	"fmt"
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"gitea.com/gitea/gitea-mcp/pkg/gitea"
 | 
						"gitea.com/gitea/gitea-mcp/pkg/gitea"
 | 
				
			||||||
@@ -11,10 +14,12 @@ import (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
	gitea_sdk "code.gitea.io/sdk/gitea"
 | 
						gitea_sdk "code.gitea.io/sdk/gitea"
 | 
				
			||||||
	"github.com/mark3labs/mcp-go/mcp"
 | 
						"github.com/mark3labs/mcp-go/mcp"
 | 
				
			||||||
 | 
						"github.com/mark3labs/mcp-go/server"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	GetFileToolName    = "get_file_content"
 | 
						GetFileToolName    = "get_file_content"
 | 
				
			||||||
 | 
						GetDirToolName     = "get_dir_content"
 | 
				
			||||||
	CreateFileToolName = "create_file"
 | 
						CreateFileToolName = "create_file"
 | 
				
			||||||
	UpdateFileToolName = "update_file"
 | 
						UpdateFileToolName = "update_file"
 | 
				
			||||||
	DeleteFileToolName = "delete_file"
 | 
						DeleteFileToolName = "delete_file"
 | 
				
			||||||
@@ -28,6 +33,16 @@ var (
 | 
				
			|||||||
		mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
							mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
				
			||||||
		mcp.WithString("ref", mcp.Required(), mcp.Description("ref can be branch/tag/commit")),
 | 
							mcp.WithString("ref", mcp.Required(), mcp.Description("ref can be branch/tag/commit")),
 | 
				
			||||||
		mcp.WithString("filePath", mcp.Required(), mcp.Description("file path")),
 | 
							mcp.WithString("filePath", mcp.Required(), mcp.Description("file path")),
 | 
				
			||||||
 | 
							mcp.WithBoolean("withLines", mcp.Description("whether to return file content with lines")),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						GetDirContentTool = mcp.NewTool(
 | 
				
			||||||
 | 
							GetDirToolName,
 | 
				
			||||||
 | 
							mcp.WithDescription("Get a list of entries in a directory"),
 | 
				
			||||||
 | 
							mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
				
			||||||
 | 
							mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
				
			||||||
 | 
							mcp.WithString("ref", mcp.Required(), mcp.Description("ref can be branch/tag/commit")),
 | 
				
			||||||
 | 
							mcp.WithString("filePath", mcp.Required(), mcp.Description("directory path")),
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	CreateFileTool = mcp.NewTool(
 | 
						CreateFileTool = mcp.NewTool(
 | 
				
			||||||
@@ -49,7 +64,7 @@ var (
 | 
				
			|||||||
		mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
							mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
				
			||||||
		mcp.WithString("filePath", mcp.Required(), mcp.Description("file path")),
 | 
							mcp.WithString("filePath", mcp.Required(), mcp.Description("file path")),
 | 
				
			||||||
		mcp.WithString("sha", mcp.Required(), mcp.Description("sha is the SHA for the file that already exists")),
 | 
							mcp.WithString("sha", mcp.Required(), mcp.Description("sha is the SHA for the file that already exists")),
 | 
				
			||||||
		mcp.WithString("content", mcp.Required(), mcp.Description("file content, base64 encoded")),
 | 
							mcp.WithString("content", mcp.Required(), mcp.Description("file content")),
 | 
				
			||||||
		mcp.WithString("message", mcp.Required(), mcp.Description("commit message")),
 | 
							mcp.WithString("message", mcp.Required(), mcp.Description("commit message")),
 | 
				
			||||||
		mcp.WithString("branch_name", mcp.Required(), mcp.Description("branch name")),
 | 
							mcp.WithString("branch_name", mcp.Required(), mcp.Description("branch name")),
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
@@ -66,45 +81,141 @@ var (
 | 
				
			|||||||
	)
 | 
						)
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						Tool.RegisterRead(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    GetFileContentTool,
 | 
				
			||||||
 | 
							Handler: GetFileContentFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterRead(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    GetDirContentTool,
 | 
				
			||||||
 | 
							Handler: GetDirContentFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterWrite(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    CreateFileTool,
 | 
				
			||||||
 | 
							Handler: CreateFileFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterWrite(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    UpdateFileTool,
 | 
				
			||||||
 | 
							Handler: UpdateFileFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterWrite(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    DeleteFileTool,
 | 
				
			||||||
 | 
							Handler: DeleteFileFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type ContentLine struct {
 | 
				
			||||||
 | 
						LineNumber int    `json:"line"`
 | 
				
			||||||
 | 
						Content    string `json:"content"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetFileContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
					func GetFileContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
	log.Debugf("Called GetFileFn")
 | 
						log.Debugf("Called GetFileFn")
 | 
				
			||||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
							return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
							return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	ref, _ := req.Params.Arguments["ref"].(string)
 | 
						ref, _ := req.GetArguments()["ref"].(string)
 | 
				
			||||||
	filePath, ok := req.Params.Arguments["filePath"].(string)
 | 
						filePath, ok := req.GetArguments()["filePath"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("filePath is required"))
 | 
							return to.ErrorResult(fmt.Errorf("filePath is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	content, _, err := gitea.Client().GetContents(owner, repo, ref, filePath)
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						content, _, err := client.GetContents(owner, repo, ref, filePath)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("get file err: %v", err))
 | 
							return to.ErrorResult(fmt.Errorf("get file err: %v", err))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
						withLines, _ := req.GetArguments()["withLines"].(bool)
 | 
				
			||||||
 | 
						if withLines {
 | 
				
			||||||
 | 
							rawContent, err := base64.StdEncoding.DecodeString(*content.Content)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return to.ErrorResult(fmt.Errorf("decode base64 content err: %v", err))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							contentLines := make([]ContentLine, 0)
 | 
				
			||||||
 | 
							line := 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							scanner := bufio.NewScanner(bytes.NewReader(rawContent))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							for scanner.Scan() {
 | 
				
			||||||
 | 
								line++
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
								contentLines = append(contentLines, ContentLine{
 | 
				
			||||||
 | 
									LineNumber: line,
 | 
				
			||||||
 | 
									Content:    scanner.Text(),
 | 
				
			||||||
 | 
								})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							if err := scanner.Err(); err != nil {
 | 
				
			||||||
 | 
								return to.ErrorResult(fmt.Errorf("scan content err: %v", err))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// remove the last blank line if exists
 | 
				
			||||||
 | 
							// git does not consider the last line as a new line
 | 
				
			||||||
 | 
							if contentLines[len(contentLines)-1].Content == "" {
 | 
				
			||||||
 | 
								contentLines = contentLines[:len(contentLines)-1]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							contentBytes, err := json.MarshalIndent(contentLines, "", "  ")
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return to.ErrorResult(fmt.Errorf("marshal content lines err: %v", err))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							contentStr := string(contentBytes)
 | 
				
			||||||
 | 
							content.Content = &contentStr
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return to.TextResult(content)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetDirContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
 | 
						log.Debugf("Called GetDirContentFn")
 | 
				
			||||||
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						ref, _ := req.GetArguments()["ref"].(string)
 | 
				
			||||||
 | 
						filePath, ok := req.GetArguments()["filePath"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("filePath is required"))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						content, _, err := client.ListContents(owner, repo, ref, filePath)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get dir content err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
	return to.TextResult(content)
 | 
						return to.TextResult(content)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func CreateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
					func CreateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
	log.Debugf("Called CreateFileFn")
 | 
						log.Debugf("Called CreateFileFn")
 | 
				
			||||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
							return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
							return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	filePath, ok := req.Params.Arguments["filePath"].(string)
 | 
						filePath, ok := req.GetArguments()["filePath"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("filePath is required"))
 | 
							return to.ErrorResult(fmt.Errorf("filePath is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	content, _ := req.Params.Arguments["content"].(string)
 | 
						content, _ := req.GetArguments()["content"].(string)
 | 
				
			||||||
	message, _ := req.Params.Arguments["message"].(string)
 | 
						message, _ := req.GetArguments()["message"].(string)
 | 
				
			||||||
	branchName, _ := req.Params.Arguments["branch_name"].(string)
 | 
						branchName, _ := req.GetArguments()["branch_name"].(string)
 | 
				
			||||||
	opt := gitea_sdk.CreateFileOptions{
 | 
						opt := gitea_sdk.CreateFileOptions{
 | 
				
			||||||
		Content: base64.StdEncoding.EncodeToString([]byte(content)),
 | 
							Content: base64.StdEncoding.EncodeToString([]byte(content)),
 | 
				
			||||||
		FileOptions: gitea_sdk.FileOptions{
 | 
							FileOptions: gitea_sdk.FileOptions{
 | 
				
			||||||
@@ -113,7 +224,11 @@ func CreateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
 | 
				
			|||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	_, _, err := gitea.Client().CreateFile(owner, repo, filePath, opt)
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						_, _, err = client.CreateFile(owner, repo, filePath, opt)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("create file err: %v", err))
 | 
							return to.ErrorResult(fmt.Errorf("create file err: %v", err))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -122,35 +237,39 @@ func CreateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func UpdateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
					func UpdateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
	log.Debugf("Called UpdateFileFn")
 | 
						log.Debugf("Called UpdateFileFn")
 | 
				
			||||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
							return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
							return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	filePath, ok := req.Params.Arguments["filePath"].(string)
 | 
						filePath, ok := req.GetArguments()["filePath"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("filePath is required"))
 | 
							return to.ErrorResult(fmt.Errorf("filePath is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	sha, ok := req.Params.Arguments["sha"].(string)
 | 
						sha, ok := req.GetArguments()["sha"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("sha is required"))
 | 
							return to.ErrorResult(fmt.Errorf("sha is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	content, _ := req.Params.Arguments["content"].(string)
 | 
						content, _ := req.GetArguments()["content"].(string)
 | 
				
			||||||
	message, _ := req.Params.Arguments["message"].(string)
 | 
						message, _ := req.GetArguments()["message"].(string)
 | 
				
			||||||
	branchName, _ := req.Params.Arguments["branch_name"].(string)
 | 
						branchName, _ := req.GetArguments()["branch_name"].(string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	opt := gitea_sdk.UpdateFileOptions{
 | 
						opt := gitea_sdk.UpdateFileOptions{
 | 
				
			||||||
		SHA:     sha,
 | 
							SHA:     sha,
 | 
				
			||||||
		Content: content,
 | 
							Content: base64.StdEncoding.EncodeToString([]byte(content)),
 | 
				
			||||||
		FileOptions: gitea_sdk.FileOptions{
 | 
							FileOptions: gitea_sdk.FileOptions{
 | 
				
			||||||
			Message:    message,
 | 
								Message:    message,
 | 
				
			||||||
			BranchName: branchName,
 | 
								BranchName: branchName,
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	_, _, err := gitea.Client().UpdateFile(owner, repo, filePath, opt)
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						_, _, err = client.UpdateFile(owner, repo, filePath, opt)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("update file err: %v", err))
 | 
							return to.ErrorResult(fmt.Errorf("update file err: %v", err))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -159,21 +278,21 @@ func UpdateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func DeleteFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
					func DeleteFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
	log.Debugf("Called DeleteFileFn")
 | 
						log.Debugf("Called DeleteFileFn")
 | 
				
			||||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
							return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
							return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	filePath, ok := req.Params.Arguments["filePath"].(string)
 | 
						filePath, ok := req.GetArguments()["filePath"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("filePath is required"))
 | 
							return to.ErrorResult(fmt.Errorf("filePath is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	message, _ := req.Params.Arguments["message"].(string)
 | 
						message, _ := req.GetArguments()["message"].(string)
 | 
				
			||||||
	branchName, _ := req.Params.Arguments["branch_name"].(string)
 | 
						branchName, _ := req.GetArguments()["branch_name"].(string)
 | 
				
			||||||
	sha, ok := req.Params.Arguments["sha"].(string)
 | 
						sha, ok := req.GetArguments()["sha"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("sha is required"))
 | 
							return to.ErrorResult(fmt.Errorf("sha is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -184,7 +303,11 @@ func DeleteFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
 | 
				
			|||||||
		},
 | 
							},
 | 
				
			||||||
		SHA: sha,
 | 
							SHA: sha,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	_, err := gitea.Client().DeleteFile(owner, repo, filePath, opt)
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						_, err = client.DeleteFile(owner, repo, filePath, opt)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("delete file err: %v", err))
 | 
							return to.ErrorResult(fmt.Errorf("delete file err: %v", err))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										286
									
								
								operation/repo/release.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										286
									
								
								operation/repo/release.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,286 @@
 | 
				
			|||||||
 | 
					package repo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
						"time"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"gitea.com/gitea/gitea-mcp/pkg/gitea"
 | 
				
			||||||
 | 
						"gitea.com/gitea/gitea-mcp/pkg/log"
 | 
				
			||||||
 | 
						"gitea.com/gitea/gitea-mcp/pkg/ptr"
 | 
				
			||||||
 | 
						"gitea.com/gitea/gitea-mcp/pkg/to"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gitea_sdk "code.gitea.io/sdk/gitea"
 | 
				
			||||||
 | 
						"github.com/mark3labs/mcp-go/mcp"
 | 
				
			||||||
 | 
						"github.com/mark3labs/mcp-go/server"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						CreateReleaseToolName    = "create_release"
 | 
				
			||||||
 | 
						DeleteReleaseToolName    = "delete_release"
 | 
				
			||||||
 | 
						GetReleaseToolName       = "get_release"
 | 
				
			||||||
 | 
						GetLatestReleaseToolName = "get_latest_release"
 | 
				
			||||||
 | 
						ListReleasesToolName     = "list_releases"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						CreateReleaseTool = mcp.NewTool(
 | 
				
			||||||
 | 
							CreateReleaseToolName,
 | 
				
			||||||
 | 
							mcp.WithDescription("Create release"),
 | 
				
			||||||
 | 
							mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
				
			||||||
 | 
							mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
				
			||||||
 | 
							mcp.WithString("tag_name", mcp.Required(), mcp.Description("tag name")),
 | 
				
			||||||
 | 
							mcp.WithString("target", mcp.Required(), mcp.Description("target commitish")),
 | 
				
			||||||
 | 
							mcp.WithString("title", mcp.Required(), mcp.Description("release title")),
 | 
				
			||||||
 | 
							mcp.WithBoolean("is_draft", mcp.Description("Whether the release is draft"), mcp.DefaultBool(false)),
 | 
				
			||||||
 | 
							mcp.WithBoolean("is_pre_release", mcp.Description("Whether the release is pre-release"), mcp.DefaultBool(false)),
 | 
				
			||||||
 | 
							mcp.WithString("body", mcp.Description("release body")),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						DeleteReleaseTool = mcp.NewTool(
 | 
				
			||||||
 | 
							DeleteReleaseToolName,
 | 
				
			||||||
 | 
							mcp.WithDescription("Delete release"),
 | 
				
			||||||
 | 
							mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
				
			||||||
 | 
							mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
				
			||||||
 | 
							mcp.WithNumber("id", mcp.Required(), mcp.Description("release id")),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						GetReleaseTool = mcp.NewTool(
 | 
				
			||||||
 | 
							GetReleaseToolName,
 | 
				
			||||||
 | 
							mcp.WithDescription("Get release"),
 | 
				
			||||||
 | 
							mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
				
			||||||
 | 
							mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
				
			||||||
 | 
							mcp.WithNumber("id", mcp.Required(), mcp.Description("release id")),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						GetLatestReleaseTool = mcp.NewTool(
 | 
				
			||||||
 | 
							GetLatestReleaseToolName,
 | 
				
			||||||
 | 
							mcp.WithDescription("Get latest release"),
 | 
				
			||||||
 | 
							mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
				
			||||||
 | 
							mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ListReleasesTool = mcp.NewTool(
 | 
				
			||||||
 | 
							ListReleasesToolName,
 | 
				
			||||||
 | 
							mcp.WithDescription("List releases"),
 | 
				
			||||||
 | 
							mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
				
			||||||
 | 
							mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
				
			||||||
 | 
							mcp.WithBoolean("is_draft", mcp.Description("Whether the release is draft"), mcp.DefaultBool(false)),
 | 
				
			||||||
 | 
							mcp.WithBoolean("is_pre_release", mcp.Description("Whether the release is pre-release"), mcp.DefaultBool(false)),
 | 
				
			||||||
 | 
							mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)),
 | 
				
			||||||
 | 
							mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(20), mcp.Min(1)),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						Tool.RegisterWrite(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    CreateReleaseTool,
 | 
				
			||||||
 | 
							Handler: CreateReleaseFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterWrite(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    DeleteReleaseTool,
 | 
				
			||||||
 | 
							Handler: DeleteReleaseFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterRead(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    GetReleaseTool,
 | 
				
			||||||
 | 
							Handler: GetReleaseFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterRead(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    GetLatestReleaseTool,
 | 
				
			||||||
 | 
							Handler: GetLatestReleaseFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterRead(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    ListReleasesTool,
 | 
				
			||||||
 | 
							Handler: ListReleasesFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// To avoid return too many tokens, we need to provide at least information as possible
 | 
				
			||||||
 | 
					// llm can call get release to get more information
 | 
				
			||||||
 | 
					type ListReleaseResult struct {
 | 
				
			||||||
 | 
						ID           int64     `json:"id"`
 | 
				
			||||||
 | 
						TagName      string    `json:"tag_name"`
 | 
				
			||||||
 | 
						Target       string    `json:"target_commitish"`
 | 
				
			||||||
 | 
						Title        string    `json:"title"`
 | 
				
			||||||
 | 
						IsDraft      bool      `json:"draft"`
 | 
				
			||||||
 | 
						IsPrerelease bool      `json:"prerelease"`
 | 
				
			||||||
 | 
						CreatedAt    time.Time `json:"created_at"`
 | 
				
			||||||
 | 
						PublishedAt  time.Time `json:"published_at"`
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func CreateReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
 | 
						log.Debugf("Called CreateReleasesFn")
 | 
				
			||||||
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("owner is required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("repo is required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						tagName, ok := req.GetArguments()["tag_name"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("tag_name is required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						target, ok := req.GetArguments()["target"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("target is required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						title, ok := req.GetArguments()["title"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("title is required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						isDraft, _ := req.GetArguments()["is_draft"].(bool)
 | 
				
			||||||
 | 
						isPreRelease, _ := req.GetArguments()["is_pre_release"].(bool)
 | 
				
			||||||
 | 
						body, _ := req.GetArguments()["body"].(string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						_, _, err = client.CreateRelease(owner, repo, gitea_sdk.CreateReleaseOption{
 | 
				
			||||||
 | 
							TagName:      tagName,
 | 
				
			||||||
 | 
							Target:       target,
 | 
				
			||||||
 | 
							Title:        title,
 | 
				
			||||||
 | 
							Note:         body,
 | 
				
			||||||
 | 
							IsDraft:      isDraft,
 | 
				
			||||||
 | 
							IsPrerelease: isPreRelease,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("create release error: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return mcp.NewToolResultText("Release Created"), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func DeleteReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
 | 
						log.Debugf("Called DeleteReleaseFn")
 | 
				
			||||||
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("owner is required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("repo is required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						id, ok := req.GetArguments()["id"].(float64)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("id is required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						_, err = client.DeleteRelease(owner, repo, int64(id))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("delete release error: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return to.TextResult("Release deleted successfully")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
 | 
						log.Debugf("Called GetReleaseFn")
 | 
				
			||||||
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("owner is required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("repo is required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						id, ok := req.GetArguments()["id"].(float64)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("id is required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						release, _, err := client.GetRelease(owner, repo, int64(id))
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("get release error: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return to.TextResult(release)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetLatestReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
 | 
						log.Debugf("Called GetLatestReleaseFn")
 | 
				
			||||||
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("owner is required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("repo is required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						release, _, err := client.GetLatestRelease(owner, repo)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("get latest release error: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return to.TextResult(release)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ListReleasesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
 | 
						log.Debugf("Called ListReleasesFn")
 | 
				
			||||||
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("owner is required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("repo is required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var pIsDraft *bool
 | 
				
			||||||
 | 
						isDraft, ok := req.GetArguments()["is_draft"].(bool)
 | 
				
			||||||
 | 
						if ok {
 | 
				
			||||||
 | 
							pIsDraft = ptr.To(isDraft)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						var pIsPreRelease *bool
 | 
				
			||||||
 | 
						isPreRelease, ok := req.GetArguments()["is_pre_release"].(bool)
 | 
				
			||||||
 | 
						if ok {
 | 
				
			||||||
 | 
							pIsPreRelease = ptr.To(isPreRelease)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						page, _ := req.GetArguments()["page"].(float64)
 | 
				
			||||||
 | 
						pageSize, _ := req.GetArguments()["pageSize"].(float64)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						releases, _, err := client.ListReleases(owner, repo, gitea_sdk.ListReleasesOptions{
 | 
				
			||||||
 | 
							ListOptions: gitea_sdk.ListOptions{
 | 
				
			||||||
 | 
								Page:     int(page),
 | 
				
			||||||
 | 
								PageSize: int(pageSize),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
							IsDraft:      pIsDraft,
 | 
				
			||||||
 | 
							IsPreRelease: pIsPreRelease,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("list releases error: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						results := make([]ListReleaseResult, len(releases))
 | 
				
			||||||
 | 
						for _, release := range releases {
 | 
				
			||||||
 | 
							results = append(results, ListReleaseResult{
 | 
				
			||||||
 | 
								ID:           release.ID,
 | 
				
			||||||
 | 
								TagName:      release.TagName,
 | 
				
			||||||
 | 
								Target:       release.Target,
 | 
				
			||||||
 | 
								Title:        release.Title,
 | 
				
			||||||
 | 
								IsDraft:      release.IsDraft,
 | 
				
			||||||
 | 
								IsPrerelease: release.IsPrerelease,
 | 
				
			||||||
 | 
								CreatedAt:    release.CreatedAt,
 | 
				
			||||||
 | 
								PublishedAt:  release.PublishedAt,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return to.TextResult(results)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -9,12 +9,15 @@ import (
 | 
				
			|||||||
	"gitea.com/gitea/gitea-mcp/pkg/log"
 | 
						"gitea.com/gitea/gitea-mcp/pkg/log"
 | 
				
			||||||
	"gitea.com/gitea/gitea-mcp/pkg/ptr"
 | 
						"gitea.com/gitea/gitea-mcp/pkg/ptr"
 | 
				
			||||||
	"gitea.com/gitea/gitea-mcp/pkg/to"
 | 
						"gitea.com/gitea/gitea-mcp/pkg/to"
 | 
				
			||||||
 | 
						"gitea.com/gitea/gitea-mcp/pkg/tool"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	gitea_sdk "code.gitea.io/sdk/gitea"
 | 
						gitea_sdk "code.gitea.io/sdk/gitea"
 | 
				
			||||||
	"github.com/mark3labs/mcp-go/mcp"
 | 
						"github.com/mark3labs/mcp-go/mcp"
 | 
				
			||||||
	"github.com/mark3labs/mcp-go/server"
 | 
						"github.com/mark3labs/mcp-go/server"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var Tool = tool.New()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	CreateRepoToolName  = "create_repo"
 | 
						CreateRepoToolName  = "create_repo"
 | 
				
			||||||
	ForkRepoToolName    = "fork_repo"
 | 
						ForkRepoToolName    = "fork_repo"
 | 
				
			||||||
@@ -24,7 +27,7 @@ const (
 | 
				
			|||||||
var (
 | 
					var (
 | 
				
			||||||
	CreateRepoTool = mcp.NewTool(
 | 
						CreateRepoTool = mcp.NewTool(
 | 
				
			||||||
		CreateRepoToolName,
 | 
							CreateRepoToolName,
 | 
				
			||||||
		mcp.WithDescription("Create repository"),
 | 
							mcp.WithDescription("Create repository in personal account or organization"),
 | 
				
			||||||
		mcp.WithString("name", mcp.Required(), mcp.Description("Name of the repository to create")),
 | 
							mcp.WithString("name", mcp.Required(), mcp.Description("Name of the repository to create")),
 | 
				
			||||||
		mcp.WithString("description", mcp.Description("Description of the repository to create")),
 | 
							mcp.WithString("description", mcp.Description("Description of the repository to create")),
 | 
				
			||||||
		mcp.WithBoolean("private", mcp.Description("Whether the repository is private")),
 | 
							mcp.WithBoolean("private", mcp.Description("Whether the repository is private")),
 | 
				
			||||||
@@ -35,6 +38,7 @@ var (
 | 
				
			|||||||
		mcp.WithString("license", mcp.Description("License to use")),
 | 
							mcp.WithString("license", mcp.Description("License to use")),
 | 
				
			||||||
		mcp.WithString("readme", mcp.Description("Readme of the repository to create")),
 | 
							mcp.WithString("readme", mcp.Description("Readme of the repository to create")),
 | 
				
			||||||
		mcp.WithString("default_branch", mcp.Description("DefaultBranch of the repository (used when initializes and in template)")),
 | 
							mcp.WithString("default_branch", mcp.Description("DefaultBranch of the repository (used when initializes and in template)")),
 | 
				
			||||||
 | 
							mcp.WithString("organization", mcp.Description("Organization name to create repository in (optional - defaults to personal account)")),
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	ForkRepoTool = mcp.NewTool(
 | 
						ForkRepoTool = mcp.NewTool(
 | 
				
			||||||
@@ -54,6 +58,21 @@ var (
 | 
				
			|||||||
	)
 | 
						)
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						Tool.RegisterWrite(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    CreateRepoTool,
 | 
				
			||||||
 | 
							Handler: CreateRepoFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterWrite(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    ForkRepoTool,
 | 
				
			||||||
 | 
							Handler: ForkRepoFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterRead(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    ListMyReposTool,
 | 
				
			||||||
 | 
							Handler: ListMyReposFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func RegisterTool(s *server.MCPServer) {
 | 
					func RegisterTool(s *server.MCPServer) {
 | 
				
			||||||
	s.AddTool(CreateRepoTool, CreateRepoFn)
 | 
						s.AddTool(CreateRepoTool, CreateRepoFn)
 | 
				
			||||||
	s.AddTool(ForkRepoTool, ForkRepoFn)
 | 
						s.AddTool(ForkRepoTool, ForkRepoFn)
 | 
				
			||||||
@@ -70,25 +89,39 @@ func RegisterTool(s *server.MCPServer) {
 | 
				
			|||||||
	s.AddTool(DeleteBranchTool, DeleteBranchFn)
 | 
						s.AddTool(DeleteBranchTool, DeleteBranchFn)
 | 
				
			||||||
	s.AddTool(ListBranchesTool, ListBranchesFn)
 | 
						s.AddTool(ListBranchesTool, ListBranchesFn)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Release
 | 
				
			||||||
 | 
						s.AddTool(CreateReleaseTool, CreateReleaseFn)
 | 
				
			||||||
 | 
						s.AddTool(DeleteReleaseTool, DeleteReleaseFn)
 | 
				
			||||||
 | 
						s.AddTool(GetReleaseTool, GetReleaseFn)
 | 
				
			||||||
 | 
						s.AddTool(GetLatestReleaseTool, GetLatestReleaseFn)
 | 
				
			||||||
 | 
						s.AddTool(ListReleasesTool, ListReleasesFn)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// Tag
 | 
				
			||||||
 | 
						s.AddTool(CreateTagTool, CreateTagFn)
 | 
				
			||||||
 | 
						s.AddTool(DeleteTagTool, DeleteTagFn)
 | 
				
			||||||
 | 
						s.AddTool(GetTagTool, GetTagFn)
 | 
				
			||||||
 | 
						s.AddTool(ListTagsTool, ListTagsFn)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	// Commit
 | 
						// Commit
 | 
				
			||||||
	s.AddTool(ListRepoCommitsTool, ListRepoCommitsFn)
 | 
						s.AddTool(ListRepoCommitsTool, ListRepoCommitsFn)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func CreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
					func CreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
	log.Debugf("Called CreateRepoFn")
 | 
						log.Debugf("Called CreateRepoFn")
 | 
				
			||||||
	name, ok := req.Params.Arguments["name"].(string)
 | 
						name, ok := req.GetArguments()["name"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(errors.New("repository name is required"))
 | 
							return to.ErrorResult(errors.New("repository name is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	description, _ := req.Params.Arguments["description"].(string)
 | 
						description, _ := req.GetArguments()["description"].(string)
 | 
				
			||||||
	private, _ := req.Params.Arguments["private"].(bool)
 | 
						private, _ := req.GetArguments()["private"].(bool)
 | 
				
			||||||
	issueLabels, _ := req.Params.Arguments["issue_labels"].(string)
 | 
						issueLabels, _ := req.GetArguments()["issue_labels"].(string)
 | 
				
			||||||
	autoInit, _ := req.Params.Arguments["auto_init"].(bool)
 | 
						autoInit, _ := req.GetArguments()["auto_init"].(bool)
 | 
				
			||||||
	template, _ := req.Params.Arguments["template"].(bool)
 | 
						template, _ := req.GetArguments()["template"].(bool)
 | 
				
			||||||
	gitignores, _ := req.Params.Arguments["gitignores"].(string)
 | 
						gitignores, _ := req.GetArguments()["gitignores"].(string)
 | 
				
			||||||
	license, _ := req.Params.Arguments["license"].(string)
 | 
						license, _ := req.GetArguments()["license"].(string)
 | 
				
			||||||
	readme, _ := req.Params.Arguments["readme"].(string)
 | 
						readme, _ := req.GetArguments()["readme"].(string)
 | 
				
			||||||
	defaultBranch, _ := req.Params.Arguments["default_branch"].(string)
 | 
						defaultBranch, _ := req.GetArguments()["default_branch"].(string)
 | 
				
			||||||
 | 
						organization, _ := req.GetArguments()["organization"].(string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	opt := gitea_sdk.CreateRepoOption{
 | 
						opt := gitea_sdk.CreateRepoOption{
 | 
				
			||||||
		Name:          name,
 | 
							Name:          name,
 | 
				
			||||||
@@ -102,29 +135,42 @@ func CreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
 | 
				
			|||||||
		Readme:        readme,
 | 
							Readme:        readme,
 | 
				
			||||||
		DefaultBranch: defaultBranch,
 | 
							DefaultBranch: defaultBranch,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	repo, _, err := gitea.Client().CreateRepo(opt)
 | 
					
 | 
				
			||||||
 | 
						var repo *gitea_sdk.Repository
 | 
				
			||||||
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("create repo err: %v", err))
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						if organization != "" {
 | 
				
			||||||
 | 
							repo, _, err = client.CreateOrgRepo(organization, opt)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return to.ErrorResult(fmt.Errorf("create organization repository '%s' in '%s' err: %v", name, organization, err))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						} else {
 | 
				
			||||||
 | 
							repo, _, err = client.CreateRepo(opt)
 | 
				
			||||||
 | 
							if err != nil {
 | 
				
			||||||
 | 
								return to.ErrorResult(fmt.Errorf("create repository '%s' err: %v", name, err))
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return to.TextResult(repo)
 | 
						return to.TextResult(repo)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func ForkRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
					func ForkRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
	log.Debugf("Called ForkRepoFn")
 | 
						log.Debugf("Called ForkRepoFn")
 | 
				
			||||||
	user, ok := req.Params.Arguments["user"].(string)
 | 
						user, ok := req.GetArguments()["user"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(errors.New("user name is required"))
 | 
							return to.ErrorResult(errors.New("user name is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(errors.New("repository name is required"))
 | 
							return to.ErrorResult(errors.New("repository name is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	organization, ok := req.Params.Arguments["organization"].(string)
 | 
						organization, ok := req.GetArguments()["organization"].(string)
 | 
				
			||||||
	organizationPtr := ptr.To(organization)
 | 
						organizationPtr := ptr.To(organization)
 | 
				
			||||||
	if !ok || organization == "" {
 | 
						if !ok || organization == "" {
 | 
				
			||||||
		organizationPtr = nil
 | 
							organizationPtr = nil
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	name, ok := req.Params.Arguments["name"].(string)
 | 
						name, ok := req.GetArguments()["name"].(string)
 | 
				
			||||||
	namePtr := ptr.To(name)
 | 
						namePtr := ptr.To(name)
 | 
				
			||||||
	if !ok || name == "" {
 | 
						if !ok || name == "" {
 | 
				
			||||||
		namePtr = nil
 | 
							namePtr = nil
 | 
				
			||||||
@@ -133,20 +179,24 @@ func ForkRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResu
 | 
				
			|||||||
		Organization: organizationPtr,
 | 
							Organization: organizationPtr,
 | 
				
			||||||
		Name:         namePtr,
 | 
							Name:         namePtr,
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	_, _, err := gitea.Client().CreateFork(user, repo, opt)
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("fork repository error %v", err))
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						_, _, err = client.CreateFork(user, repo, opt)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("fork repository error: %v", err))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	return to.TextResult("Fork success")
 | 
						return to.TextResult("Fork success")
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func ListMyReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
					func ListMyReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
	log.Debugf("Called ListMyReposFn")
 | 
						log.Debugf("Called ListMyReposFn")
 | 
				
			||||||
	page, ok := req.Params.Arguments["page"].(float64)
 | 
						page, ok := req.GetArguments()["page"].(float64)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		page = 1
 | 
							page = 1
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	pageSize, ok := req.Params.Arguments["pageSize"].(float64)
 | 
						pageSize, ok := req.GetArguments()["pageSize"].(float64)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		pageSize = 100
 | 
							pageSize = 100
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -156,7 +206,11 @@ func ListMyReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
 | 
				
			|||||||
			PageSize: int(pageSize),
 | 
								PageSize: int(pageSize),
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	repos, _, err := gitea.Client().ListMyRepos(opt)
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						repos, _, err := client.ListMyRepos(opt)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("list my repositories error: %v", err))
 | 
							return to.ErrorResult(fmt.Errorf("list my repositories error: %v", err))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										211
									
								
								operation/repo/tag.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										211
									
								
								operation/repo/tag.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,211 @@
 | 
				
			|||||||
 | 
					package repo
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"context"
 | 
				
			||||||
 | 
						"fmt"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						"gitea.com/gitea/gitea-mcp/pkg/gitea"
 | 
				
			||||||
 | 
						"gitea.com/gitea/gitea-mcp/pkg/log"
 | 
				
			||||||
 | 
						"gitea.com/gitea/gitea-mcp/pkg/to"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gitea_sdk "code.gitea.io/sdk/gitea"
 | 
				
			||||||
 | 
						"github.com/mark3labs/mcp-go/mcp"
 | 
				
			||||||
 | 
						"github.com/mark3labs/mcp-go/server"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						CreateTagToolName = "create_tag"
 | 
				
			||||||
 | 
						DeleteTagToolName = "delete_tag"
 | 
				
			||||||
 | 
						GetTagToolName    = "get_tag"
 | 
				
			||||||
 | 
						ListTagsToolName  = "list_tags"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var (
 | 
				
			||||||
 | 
						CreateTagTool = mcp.NewTool(
 | 
				
			||||||
 | 
							CreateTagToolName,
 | 
				
			||||||
 | 
							mcp.WithDescription("Create tag"),
 | 
				
			||||||
 | 
							mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
				
			||||||
 | 
							mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
				
			||||||
 | 
							mcp.WithString("tag_name", mcp.Required(), mcp.Description("tag name")),
 | 
				
			||||||
 | 
							mcp.WithString("target", mcp.Description("target commitish"), mcp.DefaultString("")),
 | 
				
			||||||
 | 
							mcp.WithString("message", mcp.Description("tag message"), mcp.DefaultString("")),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						DeleteTagTool = mcp.NewTool(
 | 
				
			||||||
 | 
							DeleteTagToolName,
 | 
				
			||||||
 | 
							mcp.WithDescription("Delete tag"),
 | 
				
			||||||
 | 
							mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
				
			||||||
 | 
							mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
				
			||||||
 | 
							mcp.WithString("tag_name", mcp.Required(), mcp.Description("tag name")),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						GetTagTool = mcp.NewTool(
 | 
				
			||||||
 | 
							GetTagToolName,
 | 
				
			||||||
 | 
							mcp.WithDescription("Get tag"),
 | 
				
			||||||
 | 
							mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
				
			||||||
 | 
							mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
				
			||||||
 | 
							mcp.WithString("tag_name", mcp.Required(), mcp.Description("tag name")),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						ListTagsTool = mcp.NewTool(
 | 
				
			||||||
 | 
							ListTagsToolName,
 | 
				
			||||||
 | 
							mcp.WithDescription("List tags"),
 | 
				
			||||||
 | 
							mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
				
			||||||
 | 
							mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
				
			||||||
 | 
							mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)),
 | 
				
			||||||
 | 
							mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(20), mcp.Min(1)),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						Tool.RegisterWrite(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    CreateTagTool,
 | 
				
			||||||
 | 
							Handler: CreateTagFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterWrite(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    DeleteTagTool,
 | 
				
			||||||
 | 
							Handler: DeleteTagFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterRead(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    GetTagTool,
 | 
				
			||||||
 | 
							Handler: GetTagFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterRead(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    ListTagsTool,
 | 
				
			||||||
 | 
							Handler: ListTagsFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// To avoid return too many tokens, we need to provide at least information as possible
 | 
				
			||||||
 | 
					// llm can call get tag to get more information
 | 
				
			||||||
 | 
					type ListTagResult struct {
 | 
				
			||||||
 | 
						ID     string                `json:"id"`
 | 
				
			||||||
 | 
						Name   string                `json:"name"`
 | 
				
			||||||
 | 
						Commit *gitea_sdk.CommitMeta `json:"commit"`
 | 
				
			||||||
 | 
						// message may be a long text, so we should not provide it here
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func CreateTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
 | 
						log.Debugf("Called CreateTagFn")
 | 
				
			||||||
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("owner is required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("repo is required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						tagName, ok := req.GetArguments()["tag_name"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("tag_name is required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						target, _ := req.GetArguments()["target"].(string)
 | 
				
			||||||
 | 
						message, _ := req.GetArguments()["message"].(string)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						_, _, err = client.CreateTag(owner, repo, gitea_sdk.CreateTagOption{
 | 
				
			||||||
 | 
							TagName: tagName,
 | 
				
			||||||
 | 
							Target:  target,
 | 
				
			||||||
 | 
							Message: message,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("create tag error: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return mcp.NewToolResultText("Tag Created"), nil
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func DeleteTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
 | 
						log.Debugf("Called DeleteTagFn")
 | 
				
			||||||
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("owner is required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("repo is required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						tagName, ok := req.GetArguments()["tag_name"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("tag_name is required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						_, err = client.DeleteTag(owner, repo, tagName)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("delete tag error: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return to.TextResult("Tag deleted")
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func GetTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
 | 
						log.Debugf("Called GetTagFn")
 | 
				
			||||||
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("owner is required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("repo is required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						tagName, ok := req.GetArguments()["tag_name"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("tag_name is required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						tag, _, err := client.GetTag(owner, repo, tagName)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("get tag error: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						return to.TextResult(tag)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func ListTagsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
 | 
						log.Debugf("Called ListTagsFn")
 | 
				
			||||||
 | 
						owner, ok := req.GetArguments()["owner"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("owner is required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						repo, ok := req.GetArguments()["repo"].(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("repo is required")
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						page, _ := req.GetArguments()["page"].(float64)
 | 
				
			||||||
 | 
						pageSize, _ := req.GetArguments()["pageSize"].(float64)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						tags, _, err := client.ListRepoTags(owner, repo, gitea_sdk.ListRepoTagsOptions{
 | 
				
			||||||
 | 
							ListOptions: gitea_sdk.ListOptions{
 | 
				
			||||||
 | 
								Page:     int(page),
 | 
				
			||||||
 | 
								PageSize: int(pageSize),
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return nil, fmt.Errorf("list tags error: %v", err)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						results := make([]ListTagResult, 0, len(tags))
 | 
				
			||||||
 | 
						for _, tag := range tags {
 | 
				
			||||||
 | 
							results = append(results, ListTagResult{
 | 
				
			||||||
 | 
								ID:     tag.ID,
 | 
				
			||||||
 | 
								Name:   tag.Name,
 | 
				
			||||||
 | 
								Commit: tag.Commit,
 | 
				
			||||||
 | 
							})
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return to.TextResult(results)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
@@ -8,12 +8,15 @@ import (
 | 
				
			|||||||
	"gitea.com/gitea/gitea-mcp/pkg/log"
 | 
						"gitea.com/gitea/gitea-mcp/pkg/log"
 | 
				
			||||||
	"gitea.com/gitea/gitea-mcp/pkg/ptr"
 | 
						"gitea.com/gitea/gitea-mcp/pkg/ptr"
 | 
				
			||||||
	"gitea.com/gitea/gitea-mcp/pkg/to"
 | 
						"gitea.com/gitea/gitea-mcp/pkg/to"
 | 
				
			||||||
 | 
						"gitea.com/gitea/gitea-mcp/pkg/tool"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	gitea_sdk "code.gitea.io/sdk/gitea"
 | 
						gitea_sdk "code.gitea.io/sdk/gitea"
 | 
				
			||||||
	"github.com/mark3labs/mcp-go/mcp"
 | 
						"github.com/mark3labs/mcp-go/mcp"
 | 
				
			||||||
	"github.com/mark3labs/mcp-go/server"
 | 
						"github.com/mark3labs/mcp-go/server"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var Tool = tool.New()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	SearchUsersToolName    = "search_users"
 | 
						SearchUsersToolName    = "search_users"
 | 
				
			||||||
	SearchOrgTeamsToolName = "search_org_teams"
 | 
						SearchOrgTeamsToolName = "search_org_teams"
 | 
				
			||||||
@@ -55,23 +58,32 @@ var (
 | 
				
			|||||||
	)
 | 
						)
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func RegisterTool(s *server.MCPServer) {
 | 
					func init() {
 | 
				
			||||||
	s.AddTool(SearchUsersTool, SearchUsersFn)
 | 
						Tool.RegisterRead(server.ServerTool{
 | 
				
			||||||
	s.AddTool(SearOrgTeamsTool, SearchOrgTeamsFn)
 | 
							Tool:    SearchUsersTool,
 | 
				
			||||||
	s.AddTool(SearchReposTool, SearchReposFn)
 | 
							Handler: SearchUsersFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterRead(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    SearOrgTeamsTool,
 | 
				
			||||||
 | 
							Handler: SearchOrgTeamsFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
 | 
						Tool.RegisterRead(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    SearchReposTool,
 | 
				
			||||||
 | 
							Handler: SearchReposFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func SearchUsersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
					func SearchUsersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
	log.Debugf("Called SearchUsersFn")
 | 
						log.Debugf("Called SearchUsersFn")
 | 
				
			||||||
	keyword, ok := req.Params.Arguments["keyword"].(string)
 | 
						keyword, ok := req.GetArguments()["keyword"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("keyword is required"))
 | 
							return to.ErrorResult(fmt.Errorf("keyword is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	page, ok := req.Params.Arguments["page"].(float64)
 | 
						page, ok := req.GetArguments()["page"].(float64)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		page = 1
 | 
							page = 1
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	pageSize, ok := req.Params.Arguments["pageSize"].(float64)
 | 
						pageSize, ok := req.GetArguments()["pageSize"].(float64)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		pageSize = 100
 | 
							pageSize = 100
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -82,7 +94,11 @@ func SearchUsersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
 | 
				
			|||||||
			PageSize: int(pageSize),
 | 
								PageSize: int(pageSize),
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	users, _, err := gitea.Client().SearchUsers(opt)
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						users, _, err := client.SearchUsers(opt)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("search users err: %v", err))
 | 
							return to.ErrorResult(fmt.Errorf("search users err: %v", err))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -91,20 +107,20 @@ func SearchUsersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func SearchOrgTeamsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
					func SearchOrgTeamsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
	log.Debugf("Called SearchOrgTeamsFn")
 | 
						log.Debugf("Called SearchOrgTeamsFn")
 | 
				
			||||||
	org, ok := req.Params.Arguments["org"].(string)
 | 
						org, ok := req.GetArguments()["org"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("organization is required"))
 | 
							return to.ErrorResult(fmt.Errorf("organization is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	query, ok := req.Params.Arguments["query"].(string)
 | 
						query, ok := req.GetArguments()["query"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("query is required"))
 | 
							return to.ErrorResult(fmt.Errorf("query is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	includeDescription, _ := req.Params.Arguments["includeDescription"].(bool)
 | 
						includeDescription, _ := req.GetArguments()["includeDescription"].(bool)
 | 
				
			||||||
	page, ok := req.Params.Arguments["page"].(float64)
 | 
						page, ok := req.GetArguments()["page"].(float64)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		page = 1
 | 
							page = 1
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	pageSize, ok := req.Params.Arguments["pageSize"].(float64)
 | 
						pageSize, ok := req.GetArguments()["pageSize"].(float64)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		pageSize = 100
 | 
							pageSize = 100
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -116,7 +132,11 @@ func SearchOrgTeamsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
 | 
				
			|||||||
			PageSize: int(pageSize),
 | 
								PageSize: int(pageSize),
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	teams, _, err := gitea.Client().SearchOrgTeams(org, &opt)
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						teams, _, err := client.SearchOrgTeams(org, &opt)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("search organization teams error: %v", err))
 | 
							return to.ErrorResult(fmt.Errorf("search organization teams error: %v", err))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -125,22 +145,30 @@ func SearchOrgTeamsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func SearchReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
					func SearchReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
	log.Debugf("Called SearchReposFn")
 | 
						log.Debugf("Called SearchReposFn")
 | 
				
			||||||
	keyword, ok := req.Params.Arguments["keyword"].(string)
 | 
						keyword, ok := req.GetArguments()["keyword"].(string)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("keyword is required"))
 | 
							return to.ErrorResult(fmt.Errorf("keyword is required"))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	keywordIsTopic, _ := req.Params.Arguments["keywordIsTopic"].(bool)
 | 
						keywordIsTopic, _ := req.GetArguments()["keywordIsTopic"].(bool)
 | 
				
			||||||
	keywordInDescription, _ := req.Params.Arguments["keywordInDescription"].(bool)
 | 
						keywordInDescription, _ := req.GetArguments()["keywordInDescription"].(bool)
 | 
				
			||||||
	ownerID, _ := req.Params.Arguments["ownerID"].(float64)
 | 
						ownerID, _ := req.GetArguments()["ownerID"].(float64)
 | 
				
			||||||
	isPrivate, _ := req.Params.Arguments["isPrivate"].(bool)
 | 
						var pIsPrivate *bool
 | 
				
			||||||
	isArchived, _ := req.Params.Arguments["isArchived"].(bool)
 | 
						isPrivate, ok := req.GetArguments()["isPrivate"].(bool)
 | 
				
			||||||
	sort, _ := req.Params.Arguments["sort"].(string)
 | 
						if ok {
 | 
				
			||||||
	order, _ := req.Params.Arguments["order"].(string)
 | 
							pIsPrivate = ptr.To(isPrivate)
 | 
				
			||||||
	page, ok := req.Params.Arguments["page"].(float64)
 | 
						}
 | 
				
			||||||
 | 
						var pIsArchived *bool
 | 
				
			||||||
 | 
						isArchived, ok := req.GetArguments()["isArchived"].(bool)
 | 
				
			||||||
 | 
						if ok {
 | 
				
			||||||
 | 
							pIsArchived = ptr.To(isArchived)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						sort, _ := req.GetArguments()["sort"].(string)
 | 
				
			||||||
 | 
						order, _ := req.GetArguments()["order"].(string)
 | 
				
			||||||
 | 
						page, ok := req.GetArguments()["page"].(float64)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		page = 1
 | 
							page = 1
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	pageSize, ok := req.Params.Arguments["pageSize"].(float64)
 | 
						pageSize, ok := req.GetArguments()["pageSize"].(float64)
 | 
				
			||||||
	if !ok {
 | 
						if !ok {
 | 
				
			||||||
		pageSize = 100
 | 
							pageSize = 100
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
@@ -149,8 +177,8 @@ func SearchReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
 | 
				
			|||||||
		KeywordIsTopic:       keywordIsTopic,
 | 
							KeywordIsTopic:       keywordIsTopic,
 | 
				
			||||||
		KeywordInDescription: keywordInDescription,
 | 
							KeywordInDescription: keywordInDescription,
 | 
				
			||||||
		OwnerID:              int64(ownerID),
 | 
							OwnerID:              int64(ownerID),
 | 
				
			||||||
		IsPrivate:            ptr.To(isPrivate),
 | 
							IsPrivate:            pIsPrivate,
 | 
				
			||||||
		IsArchived:           ptr.To(isArchived),
 | 
							IsArchived:           pIsArchived,
 | 
				
			||||||
		Sort:                 sort,
 | 
							Sort:                 sort,
 | 
				
			||||||
		Order:                order,
 | 
							Order:                order,
 | 
				
			||||||
		ListOptions: gitea_sdk.ListOptions{
 | 
							ListOptions: gitea_sdk.ListOptions{
 | 
				
			||||||
@@ -158,7 +186,11 @@ func SearchReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
 | 
				
			|||||||
			PageSize: int(pageSize),
 | 
								PageSize: int(pageSize),
 | 
				
			||||||
		},
 | 
							},
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	repos, _, err := gitea.Client().SearchRepos(opt)
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						repos, _, err := client.SearchRepos(opt)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("search repos error: %v", err))
 | 
							return to.ErrorResult(fmt.Errorf("search repos error: %v", err))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,32 +7,110 @@ import (
 | 
				
			|||||||
	"gitea.com/gitea/gitea-mcp/pkg/gitea"
 | 
						"gitea.com/gitea/gitea-mcp/pkg/gitea"
 | 
				
			||||||
	"gitea.com/gitea/gitea-mcp/pkg/log"
 | 
						"gitea.com/gitea/gitea-mcp/pkg/log"
 | 
				
			||||||
	"gitea.com/gitea/gitea-mcp/pkg/to"
 | 
						"gitea.com/gitea/gitea-mcp/pkg/to"
 | 
				
			||||||
 | 
						"gitea.com/gitea/gitea-mcp/pkg/tool"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						gitea_sdk "code.gitea.io/sdk/gitea"
 | 
				
			||||||
	"github.com/mark3labs/mcp-go/mcp"
 | 
						"github.com/mark3labs/mcp-go/mcp"
 | 
				
			||||||
	"github.com/mark3labs/mcp-go/server"
 | 
						"github.com/mark3labs/mcp-go/server"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
 | 
						// GetMyUserInfoToolName is the unique tool name used for MCP registration and lookup of the get_my_user_info command.
 | 
				
			||||||
	GetMyUserInfoToolName = "get_my_user_info"
 | 
						GetMyUserInfoToolName = "get_my_user_info"
 | 
				
			||||||
 | 
						// GetUserOrgsToolName is the unique tool name used for MCP registration and lookup of the get_user_orgs command.
 | 
				
			||||||
 | 
						GetUserOrgsToolName = "get_user_orgs"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// defaultPage is the default starting page number used for paginated organization listings.
 | 
				
			||||||
 | 
						defaultPage = 1
 | 
				
			||||||
 | 
						// defaultPageSize is the default number of organizations per page for paginated queries.
 | 
				
			||||||
 | 
						defaultPageSize = 100
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// Tool is the MCP tool manager instance for registering all MCP tools in this package.
 | 
				
			||||||
 | 
					var Tool = tool.New()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var (
 | 
				
			||||||
 | 
						// GetMyUserInfoTool is the MCP tool for retrieving the current user's info.
 | 
				
			||||||
 | 
						// It is registered with a specific name and a description string.
 | 
				
			||||||
	GetMyUserInfoTool = mcp.NewTool(
 | 
						GetMyUserInfoTool = mcp.NewTool(
 | 
				
			||||||
		GetMyUserInfoToolName,
 | 
							GetMyUserInfoToolName,
 | 
				
			||||||
		mcp.WithDescription("Get my user info"),
 | 
							mcp.WithDescription("Get my user info"),
 | 
				
			||||||
	)
 | 
						)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						// GetUserOrgsTool is the MCP tool for listing organizations for the authenticated user.
 | 
				
			||||||
 | 
						// It supports pagination via "page" and "pageSize" arguments with default values specified above.
 | 
				
			||||||
 | 
						GetUserOrgsTool = mcp.NewTool(
 | 
				
			||||||
 | 
							GetUserOrgsToolName,
 | 
				
			||||||
 | 
							mcp.WithDescription("Get organizations associated with the authenticated user"),
 | 
				
			||||||
 | 
							mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(defaultPage)),
 | 
				
			||||||
 | 
							mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(defaultPageSize)),
 | 
				
			||||||
 | 
						)
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func RegisterTool(s *server.MCPServer) {
 | 
					// init registers all MCP tools in Tool at package initialization.
 | 
				
			||||||
	s.AddTool(GetMyUserInfoTool, GetUserInfoFn)
 | 
					// This function ensures the handler functions are registered before server usage.
 | 
				
			||||||
 | 
					func init() {
 | 
				
			||||||
 | 
						registerTools()
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// registerTools registers all local MCP tool definitions and their handler functions.
 | 
				
			||||||
 | 
					// To add new functionality, append your tool/handler pair to the tools slice below.
 | 
				
			||||||
 | 
					func registerTools() {
 | 
				
			||||||
 | 
						tools := []server.ServerTool{
 | 
				
			||||||
 | 
							{Tool: GetMyUserInfoTool, Handler: GetUserInfoFn},
 | 
				
			||||||
 | 
							{Tool: GetUserOrgsTool, Handler: GetUserOrgsFn},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						for _, t := range tools {
 | 
				
			||||||
 | 
							Tool.RegisterRead(t)
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// getIntArg parses an integer argument from the MCP request arguments map.
 | 
				
			||||||
 | 
					// Returns def if missing, not a number, or less than 1. Used for pagination arguments.
 | 
				
			||||||
 | 
					func getIntArg(req mcp.CallToolRequest, name string, def int) int {
 | 
				
			||||||
 | 
						val, ok := req.GetArguments()[name].(float64)
 | 
				
			||||||
 | 
						if !ok || val < 1 {
 | 
				
			||||||
 | 
							return def
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return int(val)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetUserInfoFn is the handler for "get_my_user_info" MCP tool requests.
 | 
				
			||||||
 | 
					// Logs invocation, fetches current user info from gitea, wraps result for MCP.
 | 
				
			||||||
func GetUserInfoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
					func GetUserInfoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
	log.Debugf("Called GetUserInfoFn")
 | 
						log.Debugf("[User] Called GetUserInfoFn")
 | 
				
			||||||
	user, _, err := gitea.Client().GetMyUserInfo()
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						user, _, err := client.GetMyUserInfo()
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
		return to.ErrorResult(fmt.Errorf("get user info err: %v", err))
 | 
							return to.ErrorResult(fmt.Errorf("get user info err: %v", err))
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					 | 
				
			||||||
	return to.TextResult(user)
 | 
						return to.TextResult(user)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// GetUserOrgsFn is the handler for "get_user_orgs" MCP tool requests.
 | 
				
			||||||
 | 
					// Logs invocation, pulls validated pagination arguments from request,
 | 
				
			||||||
 | 
					// performs Gitea organization listing, and wraps the result for MCP.
 | 
				
			||||||
 | 
					func GetUserOrgsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
 | 
						log.Debugf("[User] Called GetUserOrgsFn")
 | 
				
			||||||
 | 
						page := getIntArg(req, "page", defaultPage)
 | 
				
			||||||
 | 
						pageSize := getIntArg(req, "pageSize", defaultPageSize)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						opt := gitea_sdk.ListOrgsOptions{
 | 
				
			||||||
 | 
							ListOptions: gitea_sdk.ListOptions{
 | 
				
			||||||
 | 
								Page:     page,
 | 
				
			||||||
 | 
								PageSize: pageSize,
 | 
				
			||||||
 | 
							},
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						client, err := gitea.ClientFromContext(ctx)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						orgs, _, err := client.ListMyOrgs(opt)
 | 
				
			||||||
 | 
						if err != nil {
 | 
				
			||||||
 | 
							return to.ErrorResult(fmt.Errorf("get user orgs err: %v", err))
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return to.TextResult(orgs)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -7,24 +7,28 @@ import (
 | 
				
			|||||||
	"gitea.com/gitea/gitea-mcp/pkg/flag"
 | 
						"gitea.com/gitea/gitea-mcp/pkg/flag"
 | 
				
			||||||
	"gitea.com/gitea/gitea-mcp/pkg/log"
 | 
						"gitea.com/gitea/gitea-mcp/pkg/log"
 | 
				
			||||||
	"gitea.com/gitea/gitea-mcp/pkg/to"
 | 
						"gitea.com/gitea/gitea-mcp/pkg/to"
 | 
				
			||||||
 | 
						"gitea.com/gitea/gitea-mcp/pkg/tool"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"github.com/mark3labs/mcp-go/mcp"
 | 
						"github.com/mark3labs/mcp-go/mcp"
 | 
				
			||||||
	"github.com/mark3labs/mcp-go/server"
 | 
						"github.com/mark3labs/mcp-go/server"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					var Tool = tool.New()
 | 
				
			||||||
 | 
					
 | 
				
			||||||
const (
 | 
					const (
 | 
				
			||||||
	GetGiteaMCPServerVersion = "get_gitea_mcp_server_version"
 | 
						GetGiteaMCPServerVersion = "get_gitea_mcp_server_version"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					var GetGiteaMCPServerVersionTool = mcp.NewTool(
 | 
				
			||||||
	GetGiteaMCPServerVersionTool = mcp.NewTool(
 | 
					 | 
				
			||||||
	GetGiteaMCPServerVersion,
 | 
						GetGiteaMCPServerVersion,
 | 
				
			||||||
	mcp.WithDescription("Get Gitea MCP Server Version"),
 | 
						mcp.WithDescription("Get Gitea MCP Server Version"),
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
func RegisterTool(s *server.MCPServer) {
 | 
					func init() {
 | 
				
			||||||
	s.AddTool(GetGiteaMCPServerVersionTool, GetGiteaMCPServerVersionFn)
 | 
						Tool.RegisterRead(server.ServerTool{
 | 
				
			||||||
 | 
							Tool:    GetGiteaMCPServerVersionTool,
 | 
				
			||||||
 | 
							Handler: GetGiteaMCPServerVersionFn,
 | 
				
			||||||
 | 
						})
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func GetGiteaMCPServerVersionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
					func GetGiteaMCPServerVersionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										7
									
								
								pkg/context/context.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								pkg/context/context.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					package context
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type contextKey string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const (
 | 
				
			||||||
 | 
						TokenContextKey = contextKey("token")
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
@@ -7,5 +7,7 @@ var (
 | 
				
			|||||||
	Version string
 | 
						Version string
 | 
				
			||||||
	Mode    string
 | 
						Mode    string
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						Insecure bool
 | 
				
			||||||
 | 
						ReadOnly bool
 | 
				
			||||||
	Debug    bool
 | 
						Debug    bool
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,28 +1,47 @@
 | 
				
			|||||||
package gitea
 | 
					package gitea
 | 
				
			||||||
 | 
					
 | 
				
			||||||
import (
 | 
					import (
 | 
				
			||||||
	"sync"
 | 
						"context"
 | 
				
			||||||
 | 
						"crypto/tls"
 | 
				
			||||||
	"gitea.com/gitea/gitea-mcp/pkg/flag"
 | 
						"fmt"
 | 
				
			||||||
	"gitea.com/gitea/gitea-mcp/pkg/log"
 | 
						"net/http"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	"code.gitea.io/sdk/gitea"
 | 
						"code.gitea.io/sdk/gitea"
 | 
				
			||||||
 | 
						mcpContext "gitea.com/gitea/gitea-mcp/pkg/context"
 | 
				
			||||||
 | 
						"gitea.com/gitea/gitea-mcp/pkg/flag"
 | 
				
			||||||
)
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
var (
 | 
					func NewClient(token string) (*gitea.Client, error) {
 | 
				
			||||||
	client     *gitea.Client
 | 
						httpClient := &http.Client{
 | 
				
			||||||
	clientOnce sync.Once
 | 
							Transport: http.DefaultTransport,
 | 
				
			||||||
)
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func Client() *gitea.Client {
 | 
						opts := []gitea.ClientOption{
 | 
				
			||||||
	clientOnce.Do(func() {
 | 
							gitea.SetToken(token),
 | 
				
			||||||
		if client == nil {
 | 
						}
 | 
				
			||||||
			c, err := gitea.NewClient(flag.Host, gitea.SetToken(flag.Token))
 | 
						if flag.Insecure {
 | 
				
			||||||
 | 
							httpClient.Transport.(*http.Transport).TLSClientConfig = &tls.Config{
 | 
				
			||||||
 | 
								InsecureSkipVerify: true,
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						opts = append(opts, gitea.SetHTTPClient(httpClient))
 | 
				
			||||||
 | 
						if flag.Debug {
 | 
				
			||||||
 | 
							opts = append(opts, gitea.SetDebugMode())
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						client, err := gitea.NewClient(flag.Host, opts...)
 | 
				
			||||||
	if err != nil {
 | 
						if err != nil {
 | 
				
			||||||
				log.Fatalf("create gitea client err: %v", err)
 | 
							return nil, fmt.Errorf("create gitea client err: %w", err)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
			client = c
 | 
					
 | 
				
			||||||
 | 
						// Set user agent for the client
 | 
				
			||||||
 | 
						client.SetUserAgent(fmt.Sprintf("gitea-mcp-server/%s", flag.Version))
 | 
				
			||||||
 | 
						return client, nil
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
	})
 | 
					
 | 
				
			||||||
	return client
 | 
					func ClientFromContext(ctx context.Context) (*gitea.Client, error) {
 | 
				
			||||||
 | 
						token, ok := ctx.Value(mcpContext.TokenContextKey).(string)
 | 
				
			||||||
 | 
						if !ok {
 | 
				
			||||||
 | 
							token = flag.Token
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						return NewClient(token)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -19,7 +19,10 @@ var (
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
func Default() *zap.Logger {
 | 
					func Default() *zap.Logger {
 | 
				
			||||||
	defaultLoggerOnce.Do(func() {
 | 
						defaultLoggerOnce.Do(func() {
 | 
				
			||||||
		if defaultLogger == nil {
 | 
							if defaultLogger != nil {
 | 
				
			||||||
 | 
								return
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		ec := zap.NewProductionEncoderConfig()
 | 
							ec := zap.NewProductionEncoderConfig()
 | 
				
			||||||
		ec.EncodeTime = zapcore.TimeEncoderOfLayout(time.DateTime)
 | 
							ec.EncodeTime = zapcore.TimeEncoderOfLayout(time.DateTime)
 | 
				
			||||||
		ec.EncodeLevel = zapcore.CapitalLevelEncoder
 | 
							ec.EncodeLevel = zapcore.CapitalLevelEncoder
 | 
				
			||||||
@@ -32,14 +35,20 @@ func Default() *zap.Logger {
 | 
				
			|||||||
			home = os.TempDir()
 | 
								home = os.TempDir()
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							logDir := fmt.Sprintf("%s/.gitea-mcp", home)
 | 
				
			||||||
 | 
							if err := os.MkdirAll(logDir, 0o700); err != nil {
 | 
				
			||||||
 | 
								// Fallback to temp directory if creation fails
 | 
				
			||||||
 | 
								logDir = os.TempDir()
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		wss = append(wss, zapcore.AddSync(&lumberjack.Logger{
 | 
							wss = append(wss, zapcore.AddSync(&lumberjack.Logger{
 | 
				
			||||||
				Filename:   fmt.Sprintf("%s/.gitea-mcp/gitea-mcp.log", home),
 | 
								Filename:   fmt.Sprintf("%s/gitea-mcp.log", logDir),
 | 
				
			||||||
			MaxSize:    100,
 | 
								MaxSize:    100,
 | 
				
			||||||
			MaxBackups: 10,
 | 
								MaxBackups: 10,
 | 
				
			||||||
			MaxAge:     30,
 | 
								MaxAge:     30,
 | 
				
			||||||
		}))
 | 
							}))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
			if flag.Mode == "sse" {
 | 
							if flag.Mode == "http" || flag.Mode == "sse" {
 | 
				
			||||||
			wss = append(wss, zapcore.AddSync(os.Stdout))
 | 
								wss = append(wss, zapcore.AddSync(os.Stdout))
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -59,7 +68,6 @@ func Default() *zap.Logger {
 | 
				
			|||||||
			zap.AddCallerSkip(1),
 | 
								zap.AddCallerSkip(1),
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		defaultLogger = zap.New(core, options...)
 | 
							defaultLogger = zap.New(core, options...)
 | 
				
			||||||
		}
 | 
					 | 
				
			||||||
	})
 | 
						})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	return defaultLogger
 | 
						return defaultLogger
 | 
				
			||||||
@@ -71,8 +79,22 @@ func SetDefault(logger *zap.Logger) {
 | 
				
			|||||||
	}
 | 
						}
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func Logger() *zap.Logger {
 | 
					func New() *Logger {
 | 
				
			||||||
	return defaultLogger
 | 
						return &Logger{
 | 
				
			||||||
 | 
							defaultLogger: Default(),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Logger struct {
 | 
				
			||||||
 | 
						defaultLogger *zap.Logger
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (l *Logger) Infof(msg string, args ...any) {
 | 
				
			||||||
 | 
						l.defaultLogger.Sugar().Infof(msg, args...)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (l *Logger) Errorf(msg string, args ...any) {
 | 
				
			||||||
 | 
						l.defaultLogger.Sugar().Errorf(msg, args...)
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
func Debug(msg string, fields ...zap.Field) {
 | 
					func Debug(msg string, fields ...zap.Field) {
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										37
									
								
								pkg/tool/tool.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								pkg/tool/tool.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					package tool
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					import (
 | 
				
			||||||
 | 
						"gitea.com/gitea/gitea-mcp/pkg/flag"
 | 
				
			||||||
 | 
						"github.com/mark3labs/mcp-go/server"
 | 
				
			||||||
 | 
					)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type Tool struct {
 | 
				
			||||||
 | 
						write []server.ServerTool
 | 
				
			||||||
 | 
						read  []server.ServerTool
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func New() *Tool {
 | 
				
			||||||
 | 
						return &Tool{
 | 
				
			||||||
 | 
							write: make([]server.ServerTool, 0, 100),
 | 
				
			||||||
 | 
							read:  make([]server.ServerTool, 0, 100),
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t *Tool) RegisterWrite(s server.ServerTool) {
 | 
				
			||||||
 | 
						t.write = append(t.write, s)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t *Tool) RegisterRead(s server.ServerTool) {
 | 
				
			||||||
 | 
						t.read = append(t.read, s)
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					func (t *Tool) Tools() []server.ServerTool {
 | 
				
			||||||
 | 
						tools := make([]server.ServerTool, 0, len(t.write)+len(t.read))
 | 
				
			||||||
 | 
						if flag.ReadOnly {
 | 
				
			||||||
 | 
							tools = append(tools, t.read...)
 | 
				
			||||||
 | 
							return tools
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
						tools = append(tools, t.write...)
 | 
				
			||||||
 | 
						tools = append(tools, t.read...)
 | 
				
			||||||
 | 
						return tools
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
		Reference in New Issue
	
	Block a user