mirror of
				https://gitea.com/gitea/gitea-mcp.git
				synced 2025-11-04 04:11:50 +00:00 
			
		
		
		
	Compare commits
	
		
			74 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					058d4cd07f | ||
| 
						 | 
					ba64780c2f | ||
| 
						 | 
					6930c8ee30 | ||
| 
						 | 
					f08720a625 | ||
| 
						 | 
					f6b45fdf6e | ||
| 
						 | 
					98f908d5a1 | ||
| 
						 | 
					e4aa29b0f9 | ||
| 
						 | 
					32eaf86426 | ||
| 
						 | 
					8c028ec48b | ||
| 
						 | 
					88471b5de0 | ||
| 
						 | 
					e9840cf6c0 | ||
| 
						 | 
					95ab3a4b73 | ||
| 
						 | 
					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,19 +1,19 @@
 | 
			
		||||
{
 | 
			
		||||
    "name": "Gitea MCP DevContainer",
 | 
			
		||||
    "image": "mcr.microsoft.com/devcontainers/go:1.24-bookworm",
 | 
			
		||||
    "features": {
 | 
			
		||||
    },
 | 
			
		||||
    "customizations": {
 | 
			
		||||
      "vscode": {
 | 
			
		||||
        "settings": {},
 | 
			
		||||
        "extensions": [
 | 
			
		||||
          "editorconfig.editorconfig",
 | 
			
		||||
          "dbaeumer.vscode-eslint",
 | 
			
		||||
          "golang.go",
 | 
			
		||||
          "stylelint.vscode-stylelint",
 | 
			
		||||
          "DavidAnson.vscode-markdownlint",
 | 
			
		||||
          "github.copilot"
 | 
			
		||||
        ]
 | 
			
		||||
      }
 | 
			
		||||
  "name": "Gitea MCP DevContainer",
 | 
			
		||||
  "image": "mcr.microsoft.com/devcontainers/go:1.24-bookworm",
 | 
			
		||||
  "features": {},
 | 
			
		||||
  "customizations": {
 | 
			
		||||
    "vscode": {
 | 
			
		||||
      "settings": {},
 | 
			
		||||
      "extensions": [
 | 
			
		||||
        "editorconfig.editorconfig",
 | 
			
		||||
        "dbaeumer.vscode-eslint",
 | 
			
		||||
        "golang.go",
 | 
			
		||||
        "stylelint.vscode-stylelint",
 | 
			
		||||
        "DavidAnson.vscode-markdownlint",
 | 
			
		||||
        "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
 | 
			
		||||
        run: |
 | 
			
		||||
          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
 | 
			
		||||
        uses: docker/build-push-action@v5
 | 
			
		||||
@@ -1,41 +1,33 @@
 | 
			
		||||
name: release
 | 
			
		||||
 | 
			
		||||
on: 
 | 
			
		||||
on:
 | 
			
		||||
  push:
 | 
			
		||||
    tags:
 | 
			
		||||
      - '*'
 | 
			
		||||
      - "*"
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  release:
 | 
			
		||||
  goreleaser:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v4
 | 
			
		||||
      - name: Checkout
 | 
			
		||||
        uses: actions/checkout@v4
 | 
			
		||||
        with:
 | 
			
		||||
          fetch-depth: 0
 | 
			
		||||
      - name: setup go
 | 
			
		||||
      - name: Set up Go
 | 
			
		||||
        uses: actions/setup-go@v5
 | 
			
		||||
        with:
 | 
			
		||||
          go-version-file: 'go.mod'
 | 
			
		||||
      - name: release-build
 | 
			
		||||
        run: go build -ldflags="-s -w -X 'main.Version=${{  gitea.ref_name }}'" -o bin/mcp-gitea-${{  gitea.ref_name }}-linux-amd64
 | 
			
		||||
      - 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
 | 
			
		||||
          go-version: stable
 | 
			
		||||
      - name: Run GoReleaser
 | 
			
		||||
        uses: goreleaser/goreleaser-action@v6
 | 
			
		||||
        with:
 | 
			
		||||
          files: |-
 | 
			
		||||
            bin/**
 | 
			
		||||
          token: '${{secrets.RELEASE_TOKEN}}'
 | 
			
		||||
          distribution: goreleaser
 | 
			
		||||
          # 'latest', 'nightly', or a semver
 | 
			
		||||
          version: "~> v2"
 | 
			
		||||
          args: release --clean
 | 
			
		||||
        env:
 | 
			
		||||
          GITEA_TOKEN: ${{ secrets.GITHUB_TOKEN }}
 | 
			
		||||
          GORELEASER_FORCE_TOKEN: "gitea"
 | 
			
		||||
 | 
			
		||||
  release-image:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    env:
 | 
			
		||||
@@ -74,6 +66,8 @@ jobs:
 | 
			
		||||
            linux/amd64
 | 
			
		||||
            linux/arm64
 | 
			
		||||
          push: true
 | 
			
		||||
          build-args: |
 | 
			
		||||
            VERSION=${{ steps.meta.outputs.REPO_VERSION }}
 | 
			
		||||
          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 }}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,7 +1,5 @@
 | 
			
		||||
.idea
 | 
			
		||||
.vscode
 | 
			
		||||
 | 
			
		||||
gitea-mcp
 | 
			
		||||
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",
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										71
									
								
								CLAUDE.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								CLAUDE.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,71 @@
 | 
			
		||||
# CLAUDE.md
 | 
			
		||||
 | 
			
		||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
 | 
			
		||||
 | 
			
		||||
## Development Commands
 | 
			
		||||
 | 
			
		||||
**Build**: `make build` - Build the gitea-mcp binary
 | 
			
		||||
**Install**: `make install` - Build and install to GOPATH/bin
 | 
			
		||||
**Clean**: `make clean` - Remove build artifacts
 | 
			
		||||
**Test**: `go test ./...` - Run all tests
 | 
			
		||||
**Hot reload**: `make dev` - Start development server with hot reload (requires air)
 | 
			
		||||
**Dependencies**: `make vendor` - Tidy and verify module dependencies
 | 
			
		||||
 | 
			
		||||
## Architecture Overview
 | 
			
		||||
 | 
			
		||||
This is a **Gitea MCP (Model Context Protocol) Server** written in Go that provides MCP tools for interacting with Gitea repositories, issues, pull requests, users, and more.
 | 
			
		||||
 | 
			
		||||
**Core Components**:
 | 
			
		||||
 | 
			
		||||
- `main.go` + `cmd/cmd.go`: CLI entry point and flag parsing
 | 
			
		||||
- `operation/operation.go`: Main server setup and tool registration
 | 
			
		||||
- `pkg/tool/tool.go`: Tool registry with read/write categorization
 | 
			
		||||
- `operation/*/`: Individual tool modules (user, repo, issue, pull, search, wiki, etc.)
 | 
			
		||||
 | 
			
		||||
**Transport Modes**:
 | 
			
		||||
 | 
			
		||||
- **stdio** (default): Standard input/output for MCP clients
 | 
			
		||||
- **http**: HTTP server mode on configurable port (default 8080)
 | 
			
		||||
 | 
			
		||||
**Authentication**:
 | 
			
		||||
 | 
			
		||||
- Global token via `--token` flag or `GITEA_ACCESS_TOKEN` env var
 | 
			
		||||
- HTTP mode supports per-request Bearer token override in Authorization header
 | 
			
		||||
- Token precedence: HTTP Authorization header > CLI flag > environment variable
 | 
			
		||||
 | 
			
		||||
**Tool Organization**:
 | 
			
		||||
 | 
			
		||||
- Tools are categorized as read-only or write operations
 | 
			
		||||
- `--read-only` flag exposes only read tools
 | 
			
		||||
- Tool modules register via `Tool.RegisterRead()` and `Tool.RegisterWrite()`
 | 
			
		||||
 | 
			
		||||
**Key Configuration**:
 | 
			
		||||
 | 
			
		||||
- Default Gitea host: `https://gitea.com` (override with `--host` or `GITEA_HOST`)
 | 
			
		||||
- Environment variables can override CLI flags: `MCP_MODE`, `GITEA_READONLY`, `GITEA_DEBUG`, `GITEA_INSECURE`
 | 
			
		||||
- Logs are written to `~/.gitea-mcp/gitea-mcp.log` with rotation
 | 
			
		||||
 | 
			
		||||
## Available Tools
 | 
			
		||||
 | 
			
		||||
The server provides 40+ MCP tools covering:
 | 
			
		||||
 | 
			
		||||
- **User**: get_my_user_info, get_user_orgs, search_users
 | 
			
		||||
- **Repository**: create_repo, fork_repo, list_my_repos, search_repos
 | 
			
		||||
- **Branches/Tags**: create_branch, delete_branch, list_branches, create_tag, list_tags
 | 
			
		||||
- **Files**: get_file_content, create_file, update_file, delete_file, get_dir_content
 | 
			
		||||
- **Issues**: create_issue, list_repo_issues, create_issue_comment, edit_issue
 | 
			
		||||
- **Pull Requests**: create_pull_request, list_repo_pull_requests, get_pull_request_by_index
 | 
			
		||||
- **Releases**: create_release, list_releases, get_latest_release
 | 
			
		||||
- **Wiki**: create_wiki_page, update_wiki_page, list_wiki_pages
 | 
			
		||||
- **Search**: search_repos, search_users, search_org_teams
 | 
			
		||||
- **Version**: get_gitea_mcp_server_version
 | 
			
		||||
 | 
			
		||||
## Common Development Patterns
 | 
			
		||||
 | 
			
		||||
**Testing**: Use `go test ./operation -run TestFunctionName` for specific tests
 | 
			
		||||
 | 
			
		||||
**Token Context**: HTTP requests use `pkg/context.TokenContextKey` for request-scoped token access
 | 
			
		||||
 | 
			
		||||
**Flag Access**: All packages access configuration via global variables in `pkg/flag/flag.go`
 | 
			
		||||
 | 
			
		||||
**Graceful Shutdown**: HTTP mode implements graceful shutdown with 10-second timeout on SIGTERM/SIGINT
 | 
			
		||||
							
								
								
									
										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
 | 
			
		||||
 | 
			
		||||
COPY . .
 | 
			
		||||
RUN go mod download
 | 
			
		||||
 | 
			
		||||
RUN CGO_ENABLED=0 go build -ldflags="-s -w -X main.version=${VERSION}" -o gitea-mcp
 | 
			
		||||
 | 
			
		||||
FROM scratch
 | 
			
		||||
ARG VERSION=dev
 | 
			
		||||
ARG TARGETOS
 | 
			
		||||
ARG TARGETARCH
 | 
			
		||||
 | 
			
		||||
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"]
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										41
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						
									
										41
									
								
								Makefile
									
									
									
									
									
								
							@@ -3,25 +3,50 @@ EXECUTABLE := gitea-mcp
 | 
			
		||||
VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')
 | 
			
		||||
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
 | 
			
		||||
build:
 | 
			
		||||
build: ## Build the application.
 | 
			
		||||
	$(GO) build -v -ldflags '-s -w $(LDFLAGS)' -o $(EXECUTABLE)
 | 
			
		||||
 | 
			
		||||
## air: install air for hot reload
 | 
			
		||||
.PHONY: air
 | 
			
		||||
air:
 | 
			
		||||
air: ## Install air for hot reload.
 | 
			
		||||
	@hash air > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
 | 
			
		||||
		$(GO) install github.com/air-verse/air@latest; \
 | 
			
		||||
	fi
 | 
			
		||||
 | 
			
		||||
## dev: run the application with hot reload
 | 
			
		||||
.PHONY: dev
 | 
			
		||||
dev: air
 | 
			
		||||
dev: air ## run the application with hot reload
 | 
			
		||||
	air --build.cmd "make build" --build.bin ./gitea-mcp
 | 
			
		||||
 | 
			
		||||
## vendor: tidy and verify module dependencies
 | 
			
		||||
.PHONY: vendor
 | 
			
		||||
vendor:
 | 
			
		||||
vendor: ## tidy and verify module dependencies
 | 
			
		||||
	@echo 'Tidying and verifying module dependencies...'
 | 
			
		||||
	go mod tidy
 | 
			
		||||
	go mod verify
 | 
			
		||||
	go mod verify
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										117
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										117
									
								
								README.md
									
									
									
									
									
								
							@@ -1,8 +1,26 @@
 | 
			
		||||
# 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.
 | 
			
		||||
 | 
			
		||||
[](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?
 | 
			
		||||
 | 
			
		||||
@@ -36,7 +54,7 @@ Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "servers": {
 | 
			
		||||
      "github": {
 | 
			
		||||
      "gitea-mcp": {
 | 
			
		||||
        "command": "docker",
 | 
			
		||||
        "args": [
 | 
			
		||||
          "run",
 | 
			
		||||
@@ -44,7 +62,7 @@ Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace
 | 
			
		||||
          "--rm",
 | 
			
		||||
          "-e",
 | 
			
		||||
          "GITEA_ACCESS_TOKEN",
 | 
			
		||||
          "gitea/gitea-mcp-server"
 | 
			
		||||
          "docker.gitea.com/gitea-mcp-server"
 | 
			
		||||
        ],
 | 
			
		||||
        "env": {
 | 
			
		||||
          "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
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
 | 
			
		||||
@@ -75,12 +93,12 @@ Before building, make sure you have the following installed:
 | 
			
		||||
Then run:
 | 
			
		||||
 | 
			
		||||
```bash
 | 
			
		||||
make build
 | 
			
		||||
make install
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### 📁 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
 | 
			
		||||
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": {
 | 
			
		||||
        // "GITEA_HOST": "https://gitea.com",
 | 
			
		||||
        // "GITEA_INSECURE": "true",
 | 
			
		||||
        "GITEA_ACCESS_TOKEN": "<your personal access token>"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
@@ -114,13 +133,16 @@ To configure the MCP server for Gitea, add the following to your MCP configurati
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
- **sse mode**
 | 
			
		||||
- **http mode**
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
{
 | 
			
		||||
  "mcpServers": {
 | 
			
		||||
    "gitea": {
 | 
			
		||||
      "url": "http://localhost:8080/sse"
 | 
			
		||||
      "url": "http://localhost:8080/mcp",
 | 
			
		||||
      "headers": {
 | 
			
		||||
        "Authorization": "Bearer <your personal access token>"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -142,38 +164,63 @@ list all my repositories
 | 
			
		||||
 | 
			
		||||
The Gitea MCP Server supports the following tools:
 | 
			
		||||
 | 
			
		||||
|             Tool             |    Scope     |                      Description                      |
 | 
			
		||||
| :--------------------------: | :----------: | :---------------------------------------------------: |
 | 
			
		||||
|       get_my_user_info       |     User     |     Get the information of the authenticated user     |
 | 
			
		||||
|         create_repo          |  Repository  |                Create a new repository                |
 | 
			
		||||
|          fork_repo           |  Repository  |                   Fork a repository                   |
 | 
			
		||||
|        list_my_repos         |  Repository  | List all repositories owned by the authenticated user |
 | 
			
		||||
|        create_branch         |    Branch    |                  Create a new branch                  |
 | 
			
		||||
|        delete_branch         |    Branch    |                    Delete a branch                    |
 | 
			
		||||
|        list_branches         |    Branch    |           List all branches 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         |
 | 
			
		||||
|         create_file          |     File     |                   Create a new file                   |
 | 
			
		||||
|         update_file          |     File     |                Update an existing file                |
 | 
			
		||||
|         delete_file          |     File     |                     Delete a file                     |
 | 
			
		||||
|      get_issue_by_index      |    Issue     |               Get an issue by its index               |
 | 
			
		||||
|       list_repo_issues       |    Issue     |            List all issues in a repository            |
 | 
			
		||||
|         create_issue         |    Issue     |                  Create a new issue                   |
 | 
			
		||||
|     create_issue_comment     |    Issue     |             Create a comment on an issue              |
 | 
			
		||||
|  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         |
 | 
			
		||||
|     create_pull_request      | Pull Request |               Create a new pull request               |
 | 
			
		||||
|         search_users         |     User     |                   Search for users                    |
 | 
			
		||||
|       search_org_teams       | Organization |          Search for teams in an organization          |
 | 
			
		||||
|         search_repos         |  Repository  |                Search for repositories                |
 | 
			
		||||
| get_gitea_mcp_server_version |    Server    |        Get the version of the Gitea MCP Server        |
 | 
			
		||||
|             Tool             |    Scope     |                       Description                        |
 | 
			
		||||
| :--------------------------: | :----------: | :------------------------------------------------------: |
 | 
			
		||||
|       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                  |
 | 
			
		||||
|          fork_repo           |  Repository  |                    Fork a repository                     |
 | 
			
		||||
|        list_my_repos         |  Repository  |  List all repositories owned by the authenticated user   |
 | 
			
		||||
|        create_branch         |    Branch    |                   Create a new branch                    |
 | 
			
		||||
|        delete_branch         |    Branch    |                     Delete a branch                      |
 | 
			
		||||
|        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             |
 | 
			
		||||
|       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                     |
 | 
			
		||||
|         update_file          |     File     |                 Update an existing file                  |
 | 
			
		||||
|         delete_file          |     File     |                      Delete a file                       |
 | 
			
		||||
|      get_issue_by_index      |    Issue     |                Get an issue by its index                 |
 | 
			
		||||
|       list_repo_issues       |    Issue     |             List all issues in a repository              |
 | 
			
		||||
|         create_issue         |    Issue     |                    Create a new 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              |
 | 
			
		||||
|   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_reviewer | Pull Request |           Add reviewers to a pull request                |
 | 
			
		||||
|         search_users         |     User     |                     Search for users                     |
 | 
			
		||||
|       search_org_teams       | Organization |           Search for teams in an organization            |
 | 
			
		||||
|        list_org_labels       | Organization |            List labels defined at organization level     |
 | 
			
		||||
|        create_org_label      | Organization |                  Create a label in an organization       |
 | 
			
		||||
|         edit_org_label       | Organization |                  Edit a label in an organization         |
 | 
			
		||||
|       delete_org_label       | Organization |                 Delete a label in an organization        |
 | 
			
		||||
|         search_repos         |  Repository  |                 Search for repositories                  |
 | 
			
		||||
| get_gitea_mcp_server_version |    Server    |         Get the version of the Gitea MCP Server          |
 | 
			
		||||
|       list_wiki_pages        |     Wiki     |          List all wiki pages in a repository            |
 | 
			
		||||
|        get_wiki_page         |     Wiki     |       Get a wiki page content and metadata              |
 | 
			
		||||
|      get_wiki_revisions      |     Wiki     |        Get revisions history of a wiki page             |
 | 
			
		||||
|      create_wiki_page        |     Wiki     |                 Create a new wiki page                   |
 | 
			
		||||
|      update_wiki_page        |     Wiki     |             Update an existing wiki page                 |
 | 
			
		||||
|      delete_wiki_page        |     Wiki     |                  Delete a wiki page                      |
 | 
			
		||||
 | 
			
		||||
## 🐛 Debugging
 | 
			
		||||
 | 
			
		||||
To enable debug mode, add the `-d` flag when running the Gitea MCP Server with sse mode:
 | 
			
		||||
To enable debug mode, add the `-d` flag when running the Gitea MCP Server with http mode:
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
./gitea-mcp -t sse [--port 8080] --token <your personal access token> -d
 | 
			
		||||
./gitea-mcp -t http [--port 8080] --token <your personal access token> -d
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 🛠 Troubleshooting
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										235
									
								
								README.zh-cn.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										235
									
								
								README.zh-cn.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,235 @@
 | 
			
		||||
# 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-中使用)
 | 
			
		||||
    - [📥 下载官方二进制版本](#-下载官方二进制版本)
 | 
			
		||||
    - [🔧 从源码构建](#-从源码构建)
 | 
			
		||||
    - [📁 加入 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 二进制版本](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>"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
- **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      | 拉取请求 |       创建新拉取请求       |
 | 
			
		||||
| create_pull_request_reviewer | 拉取请求 |     为拉取请求添加审查者   |
 | 
			
		||||
|         search_users         |   用户   |          搜索用户          |
 | 
			
		||||
|       search_org_teams       |   组织   |        搜索组织团队        |
 | 
			
		||||
|       list_org_labels        |   组织   |        列出组织标签        |
 | 
			
		||||
|       create_org_label       |   组织   |        创建组织标签        |
 | 
			
		||||
|        edit_org_label        |   组织   |        编辑组织标签        |
 | 
			
		||||
|       delete_org_label       |   组织   |        删除组织标签        |
 | 
			
		||||
|         search_repos         |   仓库   |          搜索仓库          |
 | 
			
		||||
| get_gitea_mcp_server_version |  服务器  | 获取 Gitea MCP 服务器版本  |
 | 
			
		||||
|       list_wiki_pages        |   Wiki   |     列出所有 Wiki 页面     |
 | 
			
		||||
|        get_wiki_page         |   Wiki   | 获取 Wiki 页面内容和元数据 |
 | 
			
		||||
|      get_wiki_revisions      |   Wiki   |     获取 Wiki 修订历史     |
 | 
			
		||||
|       create_wiki_page       |   Wiki   |      创建新 Wiki 页面      |
 | 
			
		||||
|       update_wiki_page       |   Wiki   |     更新现有 Wiki 页面     |
 | 
			
		||||
|       delete_wiki_page       |   Wiki   |       删除 Wiki 页面       |
 | 
			
		||||
 | 
			
		||||
## 🐛 调试
 | 
			
		||||
 | 
			
		||||
启用调试模式时,请在 http 模式运行 Gitea MCP 服务器时加上 `-d` 标志:
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
./gitea-mcp -t http [--port 8080] --token <your personal access token> -d
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 🛠 疑难排解
 | 
			
		||||
 | 
			
		||||
如遇问题,可参考以下步骤:
 | 
			
		||||
 | 
			
		||||
1. **检查 PATH**:确保 `gitea-mcp` 可执行文件已在系统 PATH 目录中。
 | 
			
		||||
2. **验证依赖**:确认已安装 `make` 和 `Golang` 等必要依赖。
 | 
			
		||||
3. **检查配置**:仔细检查 MCP 配置文件是否有错误或遗漏。
 | 
			
		||||
4. **查看日志**:检查日志消息或警告以获取更多信息。
 | 
			
		||||
 | 
			
		||||
享受通过聊天探索和管理您的 Gitea 仓库!
 | 
			
		||||
							
								
								
									
										235
									
								
								README.zh-tw.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										235
									
								
								README.zh-tw.md
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,235 @@
 | 
			
		||||
# 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-中使用)
 | 
			
		||||
    - [📥 下載官方二進位版本](#-下載官方二進位版本)
 | 
			
		||||
    - [🔧 從原始碼建置](#-從原始碼建置)
 | 
			
		||||
    - [📁 加入 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 二進位版本](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>"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
- **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      | 拉取請求 |        創建新拉取請求        |
 | 
			
		||||
| create_pull_request_reviewer | 拉取請求 |      為拉取請求添加審查者    |
 | 
			
		||||
|         search_users         |   用戶   |           搜尋用戶           |
 | 
			
		||||
|       search_org_teams       |   組織   |         搜尋組織團隊         |
 | 
			
		||||
|       list_org_labels        |   組織   |         列出組織標籤         |
 | 
			
		||||
|       create_org_label       |   組織   |         創建組織標籤         |
 | 
			
		||||
|        edit_org_label        |   組織   |         編輯組織標籤         |
 | 
			
		||||
|       delete_org_label       |   組織   |         刪除組織標籤         |
 | 
			
		||||
|         search_repos         |   倉庫   |           搜尋倉庫           |
 | 
			
		||||
| get_gitea_mcp_server_version |  伺服器  |  取得 Gitea MCP 伺服器版本   |
 | 
			
		||||
|       list_wiki_pages        |   Wiki   |      列出所有 Wiki 頁面      |
 | 
			
		||||
|        get_wiki_page         |   Wiki   | 取得 Wiki 頁面內容與中繼資料 |
 | 
			
		||||
|      get_wiki_revisions      |   Wiki   |      取得 Wiki 修訂歷史      |
 | 
			
		||||
|       create_wiki_page       |   Wiki   |       創建新 Wiki 頁面       |
 | 
			
		||||
|       update_wiki_page       |   Wiki   |      更新現有 Wiki 頁面      |
 | 
			
		||||
|       delete_wiki_page       |   Wiki   |        刪除 Wiki 頁面        |
 | 
			
		||||
 | 
			
		||||
## 🐛 調試
 | 
			
		||||
 | 
			
		||||
啟用調試模式時,請在 http 模式執行 Gitea MCP 伺服器時加上 `-d` 旗標:
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
./gitea-mcp -t http [--port 8080] --token <your personal access token> -d
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## 🛠 疑難排解
 | 
			
		||||
 | 
			
		||||
如遇問題,可參考以下步驟:
 | 
			
		||||
 | 
			
		||||
1. **檢查 PATH**:確保 `gitea-mcp` 執行檔已在系統 PATH 目錄中。
 | 
			
		||||
2. **驗證依賴**:確認已安裝 `make` 與 `Golang` 等必要依賴。
 | 
			
		||||
3. **檢查設定**:仔細檢查 MCP 設定檔是否有錯誤或遺漏。
 | 
			
		||||
4. **查看日誌**:檢查日誌訊息或警告以獲取更多資訊。
 | 
			
		||||
 | 
			
		||||
享受透過聊天探索與管理您的 Gitea 倉庫!
 | 
			
		||||
							
								
								
									
										70
									
								
								cmd/cmd.go
									
									
									
									
									
								
							
							
						
						
									
										70
									
								
								cmd/cmd.go
									
									
									
									
									
								
							@@ -11,38 +11,35 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	transport string
 | 
			
		||||
	host      string
 | 
			
		||||
	port      int
 | 
			
		||||
	token     string
 | 
			
		||||
 | 
			
		||||
	debug bool
 | 
			
		||||
	host  string
 | 
			
		||||
	port  int
 | 
			
		||||
	token string
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	flag.StringVar(
 | 
			
		||||
		&transport,
 | 
			
		||||
		&flagPkg.Mode,
 | 
			
		||||
		"t",
 | 
			
		||||
		"stdio",
 | 
			
		||||
		"Transport type (stdio or sse)",
 | 
			
		||||
		"Transport type (stdio or http)",
 | 
			
		||||
	)
 | 
			
		||||
	flag.StringVar(
 | 
			
		||||
		&transport,
 | 
			
		||||
		&flagPkg.Mode,
 | 
			
		||||
		"transport",
 | 
			
		||||
		"stdio",
 | 
			
		||||
		"Transport type (stdio or sse)",
 | 
			
		||||
		"Transport type (stdio or http)",
 | 
			
		||||
	)
 | 
			
		||||
	flag.StringVar(
 | 
			
		||||
		&host,
 | 
			
		||||
		"host",
 | 
			
		||||
		"https://gitea.com",
 | 
			
		||||
		os.Getenv("GITEA_HOST"),
 | 
			
		||||
		"Gitea host",
 | 
			
		||||
	)
 | 
			
		||||
	flag.IntVar(
 | 
			
		||||
		&port,
 | 
			
		||||
		"port",
 | 
			
		||||
		8080,
 | 
			
		||||
		"sse port",
 | 
			
		||||
		"http port",
 | 
			
		||||
	)
 | 
			
		||||
	flag.StringVar(
 | 
			
		||||
		&token,
 | 
			
		||||
@@ -51,24 +48,27 @@ func init() {
 | 
			
		||||
		"Your personal access token",
 | 
			
		||||
	)
 | 
			
		||||
	flag.BoolVar(
 | 
			
		||||
		&debug,
 | 
			
		||||
		"d",
 | 
			
		||||
		true,
 | 
			
		||||
		"debug mode",
 | 
			
		||||
		&flagPkg.ReadOnly,
 | 
			
		||||
		"read-only",
 | 
			
		||||
		false,
 | 
			
		||||
		"Read-only mode",
 | 
			
		||||
	)
 | 
			
		||||
	flag.BoolVar(
 | 
			
		||||
		&debug,
 | 
			
		||||
		"debug",
 | 
			
		||||
		true,
 | 
			
		||||
		"debug mode",
 | 
			
		||||
		&flagPkg.Debug,
 | 
			
		||||
		"d",
 | 
			
		||||
		false,
 | 
			
		||||
		"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()
 | 
			
		||||
 | 
			
		||||
	flagPkg.Host = host
 | 
			
		||||
	if flagPkg.Host == "" {
 | 
			
		||||
		flagPkg.Host = os.Getenv("GITEA_HOST")
 | 
			
		||||
	}
 | 
			
		||||
	if flagPkg.Host == "" {
 | 
			
		||||
		flagPkg.Host = "https://gitea.com"
 | 
			
		||||
	}
 | 
			
		||||
@@ -80,19 +80,27 @@ func init() {
 | 
			
		||||
		flagPkg.Token = os.Getenv("GITEA_ACCESS_TOKEN")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	flagPkg.Mode = transport
 | 
			
		||||
 | 
			
		||||
	if debug {
 | 
			
		||||
		flagPkg.Debug = debug
 | 
			
		||||
	if os.Getenv("MCP_MODE") != "" {
 | 
			
		||||
		flagPkg.Mode = os.Getenv("MCP_MODE")
 | 
			
		||||
	}
 | 
			
		||||
	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()
 | 
			
		||||
	if err := operation.Run(transport, version); err != nil {
 | 
			
		||||
	if err := operation.Run(); err != nil {
 | 
			
		||||
		if err == context.Canceled {
 | 
			
		||||
			log.Info("Server shutdown due to context cancellation")
 | 
			
		||||
			return
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								config.json
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								config.json
									
									
									
									
									
								
							@@ -2,11 +2,11 @@
 | 
			
		||||
  "mcpServers": {
 | 
			
		||||
    "gitea": {
 | 
			
		||||
      "command": "gitea-mcp",
 | 
			
		||||
      "args": {
 | 
			
		||||
        "-t": "stdio",
 | 
			
		||||
        "--host": "https://gitea.com",
 | 
			
		||||
        "--token": "<your personal access token>"
 | 
			
		||||
      },
 | 
			
		||||
      "args": [
 | 
			
		||||
        "-t", "stdio",
 | 
			
		||||
        "--host", "https://gitea.com",
 | 
			
		||||
        "--token", "<your personal access token>"
 | 
			
		||||
      ]
 | 
			
		||||
      "env": {
 | 
			
		||||
        "GITEA_HOST": "https://gitea.com",
 | 
			
		||||
        "GITEA_ACCESS_TOKEN": "<your personal access token>"
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										17
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										17
									
								
								go.mod
									
									
									
									
									
								
							@@ -3,20 +3,27 @@ module gitea.com/gitea/gitea-mcp
 | 
			
		||||
go 1.24.0
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	code.gitea.io/sdk/gitea v0.21.0
 | 
			
		||||
	github.com/mark3labs/mcp-go v0.18.0
 | 
			
		||||
	code.gitea.io/sdk/gitea v0.22.1
 | 
			
		||||
	github.com/mark3labs/mcp-go v0.42.0
 | 
			
		||||
	go.uber.org/zap v1.27.0
 | 
			
		||||
	gopkg.in/natefinch/lumberjack.v2 v2.2.1
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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/go-fed/httpsig v1.1.0 // indirect
 | 
			
		||||
	github.com/google/uuid v1.6.0 // indirect
 | 
			
		||||
	github.com/hashicorp/go-version v1.7.0 // indirect
 | 
			
		||||
	github.com/invopop/jsonschema v0.13.0 // indirect
 | 
			
		||||
	github.com/mailru/easyjson v0.9.1 // indirect
 | 
			
		||||
	github.com/spf13/cast v1.10.0 // indirect
 | 
			
		||||
	github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
 | 
			
		||||
	github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
 | 
			
		||||
	go.uber.org/multierr v1.11.0 // indirect
 | 
			
		||||
	golang.org/x/crypto v0.36.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.32.0 // indirect
 | 
			
		||||
	golang.org/x/crypto v0.43.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.37.0 // indirect
 | 
			
		||||
	gopkg.in/yaml.v3 v3.0.1 // indirect
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										48
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										48
									
								
								go.sum
									
									
									
									
									
								
							@@ -1,23 +1,45 @@
 | 
			
		||||
code.gitea.io/sdk/gitea v0.21.0 h1:69n6oz6kEVHRo1+APQQyizkhrZrLsTLXey9142pfkD4=
 | 
			
		||||
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.2/go.mod h1:P/UYo7ytNBFwc+dg35IubuAUIs8zj5zzFIgUCEl55WY=
 | 
			
		||||
code.gitea.io/sdk/gitea v0.22.1 h1:7K05KjRORyTcTYULQ/AwvlVS6pawLcWyXZcTr7gHFyA=
 | 
			
		||||
code.gitea.io/sdk/gitea v0.22.1/go.mod h1:yyF5+GhljqvA30sRDreoyHILruNiy4ASufugzYg0VHM=
 | 
			
		||||
github.com/42wim/httpsig v1.2.3 h1:xb0YyWhkYj57SPtfSttIobJUPJZB9as1nsfo7KWVcEs=
 | 
			
		||||
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/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/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/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/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/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
 | 
			
		||||
github.com/mark3labs/mcp-go v0.18.0 h1:YuhgIVjNlTG2ZOwmrkORWyPTp0dz1opPEqvsPtySXao=
 | 
			
		||||
github.com/mark3labs/mcp-go v0.18.0/go.mod h1:KmJndYv7GIgcPVwEKJjNcbhVQ+hJGJhrCCB/9xITzpE=
 | 
			
		||||
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
 | 
			
		||||
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
 | 
			
		||||
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.9.1 h1:LbtsOm5WAswyWbvTEOqhypdPeZzHavpZx96/n553mR8=
 | 
			
		||||
github.com/mailru/easyjson v0.9.1/go.mod h1:1+xMtQp2MRNVL/V1bOzuP3aP8VNwRW55fQUto+XFtTU=
 | 
			
		||||
github.com/mark3labs/mcp-go v0.42.0 h1:gk/8nYJh8t3yroCAOBhNbYsM9TCKvkM13I5t5Hfu6Ls=
 | 
			
		||||
github.com/mark3labs/mcp-go v0.42.0/go.mod h1:YnJfOL382MIWDx1kMY+2zsRHU/q78dBg9aFb8W6Thdw=
 | 
			
		||||
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/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.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
 | 
			
		||||
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
 | 
			
		||||
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/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/go.mod h1:ILOh0sOhIJR3+L/8afwt/kE++YT040gmv5BQTMR2HP4=
 | 
			
		||||
go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto=
 | 
			
		||||
@@ -29,21 +51,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-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.36.0 h1:AnAEvhDddvBdpY+uR+MyHmuZzzNqXSe/GvuDeob5L34=
 | 
			
		||||
golang.org/x/crypto v0.36.0/go.mod h1:Y4J0ReaxCR1IMaabaSMugxJES1EpwhBHhv2bDHklZvc=
 | 
			
		||||
golang.org/x/crypto v0.43.0 h1:dduJYIi3A3KOfdGOHX8AVZ/jGiyPa3IbBozJ5kNuE04=
 | 
			
		||||
golang.org/x/crypto v0.43.0/go.mod h1:BFbav4mRNlXJL4wNeejLpWxB7wMbc79PdRGhWKncxR0=
 | 
			
		||||
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/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-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
 | 
			
		||||
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
 | 
			
		||||
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 | 
			
		||||
golang.org/x/sys v0.37.0 h1:fdNQudmxPjkdUTPnLn5mdQv7Zwvbvpaxqs831goi9kQ=
 | 
			
		||||
golang.org/x/sys v0.37.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks=
 | 
			
		||||
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.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
 | 
			
		||||
golang.org/x/term v0.36.0 h1:zMPR+aF8gfksFprF/Nc/rd1wRS1EI6nDBGyWAvDzx2Q=
 | 
			
		||||
golang.org/x/term v0.36.0/go.mod h1:Qu394IJq6V6dCBRgwqshf3mPF85AqzYEzofzRdZkWss=
 | 
			
		||||
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/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/go.mod h1:YD8tP3GAjkrDg1eZH7EGmyESg/lsYskCTPBJVb9jqSc=
 | 
			
		||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										9
									
								
								main.go
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								main.go
									
									
									
									
									
								
							@@ -2,12 +2,17 @@ package main
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"gitea.com/gitea/gitea-mcp/cmd"
 | 
			
		||||
	"gitea.com/gitea/gitea-mcp/pkg/flag"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	Version = "dev"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func main() {
 | 
			
		||||
	cmd.Execute(Version)
 | 
			
		||||
func init() {
 | 
			
		||||
	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/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 (
 | 
			
		||||
	GetIssueByIndexToolName    = "get_issue_by_index"
 | 
			
		||||
	ListRepoIssuesToolName     = "list_repo_issues"
 | 
			
		||||
	CreateIssueToolName        = "create_issue"
 | 
			
		||||
	CreateIssueCommentToolName = "create_issue_comment"
 | 
			
		||||
	GetIssueByIndexToolName         = "get_issue_by_index"
 | 
			
		||||
	ListRepoIssuesToolName          = "list_repo_issues"
 | 
			
		||||
	CreateIssueToolName             = "create_issue"
 | 
			
		||||
	CreateIssueCommentToolName      = "create_issue_comment"
 | 
			
		||||
	EditIssueToolName               = "edit_issue"
 | 
			
		||||
	EditIssueCommentToolName        = "edit_issue_comment"
 | 
			
		||||
	GetIssueCommentsByIndexToolName = "get_issue_comments_by_index"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
@@ -47,6 +54,7 @@ var (
 | 
			
		||||
		mcp.WithString("title", mcp.Required(), mcp.Description("issue title")),
 | 
			
		||||
		mcp.WithString("body", mcp.Required(), mcp.Description("issue body")),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	CreateIssueCommentTool = mcp.NewTool(
 | 
			
		||||
		CreateIssueCommentToolName,
 | 
			
		||||
		mcp.WithDescription("create issue comment"),
 | 
			
		||||
@@ -55,30 +63,88 @@ var (
 | 
			
		||||
		mcp.WithNumber("index", mcp.Required(), mcp.Description("repository issue index")),
 | 
			
		||||
		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")),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	EditIssueCommentTool = mcp.NewTool(
 | 
			
		||||
		EditIssueCommentToolName,
 | 
			
		||||
		mcp.WithDescription("edit issue comment"),
 | 
			
		||||
		mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
			
		||||
		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 RegisterTool(s *server.MCPServer) {
 | 
			
		||||
	s.AddTool(GetIssueByIndexTool, GetIssueByIndexFn)
 | 
			
		||||
	s.AddTool(ListRepoIssuesTool, ListRepoIssuesFn)
 | 
			
		||||
	s.AddTool(CreateIssueTool, CreateIssueFn)
 | 
			
		||||
	s.AddTool(CreateIssueCommentTool, CreateIssueCommentFn)
 | 
			
		||||
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) {
 | 
			
		||||
	log.Debugf("Called GetIssueByIndexFn")
 | 
			
		||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
			
		||||
	}
 | 
			
		||||
	index, ok := req.Params.Arguments["index"].(float64)
 | 
			
		||||
	index, ok := req.GetArguments()["index"].(float64)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		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 {
 | 
			
		||||
		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) {
 | 
			
		||||
	log.Debugf("Called ListIssuesFn")
 | 
			
		||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
			
		||||
	}
 | 
			
		||||
	state, ok := req.Params.Arguments["state"].(string)
 | 
			
		||||
	state, ok := req.GetArguments()["state"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		state = "all"
 | 
			
		||||
	}
 | 
			
		||||
	page, ok := req.Params.Arguments["page"].(float64)
 | 
			
		||||
	page, ok := req.GetArguments()["page"].(float64)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		page = 1
 | 
			
		||||
	}
 | 
			
		||||
	pageSize, ok := req.Params.Arguments["pageSize"].(float64)
 | 
			
		||||
	pageSize, ok := req.GetArguments()["pageSize"].(float64)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		pageSize = 100
 | 
			
		||||
	}
 | 
			
		||||
@@ -115,7 +181,11 @@ func ListRepoIssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
 | 
			
		||||
			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 {
 | 
			
		||||
		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) {
 | 
			
		||||
	log.Debugf("Called CreateIssueFn")
 | 
			
		||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
			
		||||
	}
 | 
			
		||||
	title, ok := req.Params.Arguments["title"].(string)
 | 
			
		||||
	title, ok := req.GetArguments()["title"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("title is required"))
 | 
			
		||||
	}
 | 
			
		||||
	body, ok := req.Params.Arguments["body"].(string)
 | 
			
		||||
	body, ok := req.GetArguments()["body"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		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,
 | 
			
		||||
		Body:  body,
 | 
			
		||||
	})
 | 
			
		||||
	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)
 | 
			
		||||
@@ -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) {
 | 
			
		||||
	log.Debugf("Called CreateIssueCommentFn")
 | 
			
		||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
			
		||||
	}
 | 
			
		||||
	index, ok := req.Params.Arguments["index"].(float64)
 | 
			
		||||
	index, ok := req.GetArguments()["index"].(float64)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("index is required"))
 | 
			
		||||
	}
 | 
			
		||||
	body, ok := req.Params.Arguments["body"].(string)
 | 
			
		||||
	body, ok := req.GetArguments()["body"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("body is required"))
 | 
			
		||||
	}
 | 
			
		||||
	opt := gitea_sdk.CreateIssueCommentOption{
 | 
			
		||||
		Body: body,
 | 
			
		||||
	}
 | 
			
		||||
	issueComment, _, err := gitea.Client().CreateIssueComment(owner, repo, int64(index), opt)
 | 
			
		||||
	client, err := gitea.ClientFromContext(ctx)
 | 
			
		||||
	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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										636
									
								
								operation/label/label.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										636
									
								
								operation/label/label.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,636 @@
 | 
			
		||||
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"
 | 
			
		||||
    ListOrgLabelsToolName      = "list_org_labels"
 | 
			
		||||
    CreateOrgLabelToolName     = "create_org_label"
 | 
			
		||||
    EditOrgLabelToolName       = "edit_org_label"
 | 
			
		||||
    DeleteOrgLabelToolName     = "delete_org_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")),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
    ListOrgLabelsTool = mcp.NewTool(
 | 
			
		||||
        ListOrgLabelsToolName,
 | 
			
		||||
        mcp.WithDescription("Lists labels defined at organization level"),
 | 
			
		||||
        mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
 | 
			
		||||
        mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
 | 
			
		||||
        mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    CreateOrgLabelTool = mcp.NewTool(
 | 
			
		||||
        CreateOrgLabelToolName,
 | 
			
		||||
        mcp.WithDescription("Creates a new label for an organization"),
 | 
			
		||||
        mcp.WithString("org", mcp.Required(), mcp.Description("organization 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")),
 | 
			
		||||
        mcp.WithBoolean("exclusive", mcp.Description("whether the label is exclusive"), mcp.DefaultBool(false)),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    EditOrgLabelTool = mcp.NewTool(
 | 
			
		||||
        EditOrgLabelToolName,
 | 
			
		||||
        mcp.WithDescription("Edits an existing organization label"),
 | 
			
		||||
        mcp.WithString("org", mcp.Required(), mcp.Description("organization 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")),
 | 
			
		||||
        mcp.WithBoolean("exclusive", mcp.Description("whether the label is exclusive")),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    DeleteOrgLabelTool = mcp.NewTool(
 | 
			
		||||
        DeleteOrgLabelToolName,
 | 
			
		||||
        mcp.WithDescription("Deletes an organization label by ID"),
 | 
			
		||||
        mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
 | 
			
		||||
        mcp.WithNumber("id", mcp.Required(), mcp.Description("label ID")),
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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,
 | 
			
		||||
	})
 | 
			
		||||
    Tool.RegisterRead(server.ServerTool{
 | 
			
		||||
        Tool:    ListOrgLabelsTool,
 | 
			
		||||
        Handler: ListOrgLabelsFn,
 | 
			
		||||
    })
 | 
			
		||||
    Tool.RegisterWrite(server.ServerTool{
 | 
			
		||||
        Tool:    CreateOrgLabelTool,
 | 
			
		||||
        Handler: CreateOrgLabelFn,
 | 
			
		||||
    })
 | 
			
		||||
    Tool.RegisterWrite(server.ServerTool{
 | 
			
		||||
        Tool:    EditOrgLabelTool,
 | 
			
		||||
        Handler: EditOrgLabelFn,
 | 
			
		||||
    })
 | 
			
		||||
    Tool.RegisterWrite(server.ServerTool{
 | 
			
		||||
        Tool:    DeleteOrgLabelTool,
 | 
			
		||||
        Handler: DeleteOrgLabelFn,
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ListOrgLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
    log.Debugf("Called ListOrgLabelsFn")
 | 
			
		||||
    org, ok := req.GetArguments()["org"].(string)
 | 
			
		||||
    if !ok {
 | 
			
		||||
        return to.ErrorResult(fmt.Errorf("org 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.ListOrgLabelsOptions{
 | 
			
		||||
        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.ListOrgLabels(org, opt)
 | 
			
		||||
    if err != nil {
 | 
			
		||||
        return to.ErrorResult(fmt.Errorf("list %v/labels err: %v", org, err))
 | 
			
		||||
    }
 | 
			
		||||
    return to.TextResult(labels)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func CreateOrgLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
    log.Debugf("Called CreateOrgLabelFn")
 | 
			
		||||
    org, ok := req.GetArguments()["org"].(string)
 | 
			
		||||
    if !ok {
 | 
			
		||||
        return to.ErrorResult(fmt.Errorf("org 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)
 | 
			
		||||
    exclusive, _ := req.GetArguments()["exclusive"].(bool)
 | 
			
		||||
 | 
			
		||||
    opt := gitea_sdk.CreateOrgLabelOption{
 | 
			
		||||
        Name:        name,
 | 
			
		||||
        Color:       color,
 | 
			
		||||
        Description: description,
 | 
			
		||||
        Exclusive:   exclusive,
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    client, err := gitea.ClientFromContext(ctx)
 | 
			
		||||
    if err != nil {
 | 
			
		||||
        return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
			
		||||
    }
 | 
			
		||||
    label, _, err := client.CreateOrgLabel(org, opt)
 | 
			
		||||
    if err != nil {
 | 
			
		||||
        return to.ErrorResult(fmt.Errorf("create %v/labels err: %v", org, err))
 | 
			
		||||
    }
 | 
			
		||||
    return to.TextResult(label)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func EditOrgLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
    log.Debugf("Called EditOrgLabelFn")
 | 
			
		||||
    org, ok := req.GetArguments()["org"].(string)
 | 
			
		||||
    if !ok {
 | 
			
		||||
        return to.ErrorResult(fmt.Errorf("org is required"))
 | 
			
		||||
    }
 | 
			
		||||
    id, ok := req.GetArguments()["id"].(float64)
 | 
			
		||||
    if !ok {
 | 
			
		||||
        return to.ErrorResult(fmt.Errorf("label ID is required"))
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    opt := gitea_sdk.EditOrgLabelOption{}
 | 
			
		||||
    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)
 | 
			
		||||
    }
 | 
			
		||||
    if exclusive, ok := req.GetArguments()["exclusive"].(bool); ok {
 | 
			
		||||
        opt.Exclusive = ptr.To(exclusive)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    client, err := gitea.ClientFromContext(ctx)
 | 
			
		||||
    if err != nil {
 | 
			
		||||
        return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
			
		||||
    }
 | 
			
		||||
    label, _, err := client.EditOrgLabel(org, int64(id), opt)
 | 
			
		||||
    if err != nil {
 | 
			
		||||
        return to.ErrorResult(fmt.Errorf("edit %v/labels/%v err: %v", org, int64(id), err))
 | 
			
		||||
    }
 | 
			
		||||
    return to.TextResult(label)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func DeleteOrgLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
    log.Debugf("Called DeleteOrgLabelFn")
 | 
			
		||||
    org, ok := req.GetArguments()["org"].(string)
 | 
			
		||||
    if !ok {
 | 
			
		||||
        return to.ErrorResult(fmt.Errorf("org 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.DeleteOrgLabel(org, int64(id))
 | 
			
		||||
    if err != nil {
 | 
			
		||||
        return to.ErrorResult(fmt.Errorf("delete %v/labels/%v err: %v", org, int64(id), err))
 | 
			
		||||
    }
 | 
			
		||||
    return to.TextResult("Label deleted successfully")
 | 
			
		||||
}
 | 
			
		||||
@@ -1,61 +1,131 @@
 | 
			
		||||
package operation
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"os"
 | 
			
		||||
	"os/signal"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"syscall"
 | 
			
		||||
	"time"
 | 
			
		||||
 | 
			
		||||
	"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/repo"
 | 
			
		||||
	"gitea.com/gitea/gitea-mcp/operation/search"
 | 
			
		||||
	"gitea.com/gitea/gitea-mcp/operation/user"
 | 
			
		||||
	"gitea.com/gitea/gitea-mcp/operation/version"
 | 
			
		||||
	"gitea.com/gitea/gitea-mcp/operation/wiki"
 | 
			
		||||
	mcpContext "gitea.com/gitea/gitea-mcp/pkg/context"
 | 
			
		||||
	"gitea.com/gitea/gitea-mcp/pkg/flag"
 | 
			
		||||
	"gitea.com/gitea/gitea-mcp/pkg/log"
 | 
			
		||||
 | 
			
		||||
	"github.com/mark3labs/mcp-go/server"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	mcpServer *server.MCPServer
 | 
			
		||||
)
 | 
			
		||||
var mcpServer *server.MCPServer
 | 
			
		||||
 | 
			
		||||
func RegisterTool(s *server.MCPServer) {
 | 
			
		||||
	// User Tool
 | 
			
		||||
	user.RegisterTool(s)
 | 
			
		||||
	s.AddTools(user.Tool.Tools()...)
 | 
			
		||||
 | 
			
		||||
	// Repo Tool
 | 
			
		||||
	repo.RegisterTool(s)
 | 
			
		||||
	s.AddTools(repo.Tool.Tools()...)
 | 
			
		||||
 | 
			
		||||
	// Issue Tool
 | 
			
		||||
	issue.RegisterTool(s)
 | 
			
		||||
	s.AddTools(issue.Tool.Tools()...)
 | 
			
		||||
 | 
			
		||||
	// Label Tool
 | 
			
		||||
	s.AddTools(label.Tool.Tools()...)
 | 
			
		||||
 | 
			
		||||
	// Pull Tool
 | 
			
		||||
	pull.RegisterTool(s)
 | 
			
		||||
	s.AddTools(pull.Tool.Tools()...)
 | 
			
		||||
 | 
			
		||||
	// Search Tool
 | 
			
		||||
	search.RegisterTool(s)
 | 
			
		||||
	s.AddTools(search.Tool.Tools()...)
 | 
			
		||||
 | 
			
		||||
	// Version Tool
 | 
			
		||||
	version.RegisterTool(s)
 | 
			
		||||
	s.AddTools(version.Tool.Tools()...)
 | 
			
		||||
 | 
			
		||||
	// Wiki Tool
 | 
			
		||||
	s.AddTools(wiki.Tool.Tools()...)
 | 
			
		||||
 | 
			
		||||
	s.DeleteTools("")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Run(transport, version string) error {
 | 
			
		||||
	flag.Version = version
 | 
			
		||||
	mcpServer = newMCPServer(version)
 | 
			
		||||
// parseBearerToken extracts the Bearer token from an Authorization header.
 | 
			
		||||
// Returns the token and true if valid, empty string and false otherwise.
 | 
			
		||||
func parseBearerToken(authHeader string) (string, bool) {
 | 
			
		||||
	const bearerPrefix = "Bearer "
 | 
			
		||||
	if len(authHeader) < len(bearerPrefix) || !strings.HasPrefix(authHeader, bearerPrefix) {
 | 
			
		||||
		return "", false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	token := strings.TrimSpace(authHeader[len(bearerPrefix):])
 | 
			
		||||
	if token == "" {
 | 
			
		||||
		return "", false
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return token, true
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getContextWithToken(ctx context.Context, r *http.Request) context.Context {
 | 
			
		||||
	authHeader := r.Header.Get("Authorization")
 | 
			
		||||
	if authHeader == "" {
 | 
			
		||||
		return ctx
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	token, ok := parseBearerToken(authHeader)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return ctx
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return context.WithValue(ctx, mcpContext.TokenContextKey, token)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Run() error {
 | 
			
		||||
	mcpServer = newMCPServer(flag.Version)
 | 
			
		||||
	RegisterTool(mcpServer)
 | 
			
		||||
	switch transport {
 | 
			
		||||
	switch flag.Mode {
 | 
			
		||||
	case "stdio":
 | 
			
		||||
		if err := server.ServeStdio(mcpServer); err != nil {
 | 
			
		||||
		if err := server.ServeStdio(
 | 
			
		||||
			mcpServer,
 | 
			
		||||
		); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	case "sse":
 | 
			
		||||
		sseServer := server.NewSSEServer(mcpServer)
 | 
			
		||||
		log.Infof("Gitea MCP SSE server listening on :%d", flag.Port)
 | 
			
		||||
		if err := sseServer.Start(fmt.Sprintf(":%d", flag.Port)); err != nil {
 | 
			
		||||
	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)
 | 
			
		||||
 | 
			
		||||
		// Graceful shutdown setup
 | 
			
		||||
		sigCh := make(chan os.Signal, 1)
 | 
			
		||||
		signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
 | 
			
		||||
		shutdownDone := make(chan struct{})
 | 
			
		||||
 | 
			
		||||
		go func() {
 | 
			
		||||
			<-sigCh
 | 
			
		||||
			log.Infof("Shutdown signal received, gracefully stopping HTTP server...")
 | 
			
		||||
			shutdownCtx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
 | 
			
		||||
			defer cancel()
 | 
			
		||||
			if err := httpServer.Shutdown(shutdownCtx); err != nil {
 | 
			
		||||
				log.Errorf("HTTP server shutdown error: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
			close(shutdownDone)
 | 
			
		||||
		}()
 | 
			
		||||
 | 
			
		||||
		if err := httpServer.Start(fmt.Sprintf(":%d", flag.Port)); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
		<-shutdownDone // Wait for shutdown to finish
 | 
			
		||||
	default:
 | 
			
		||||
		return fmt.Errorf("invalid transport type: %s. Must be 'stdio' or 'sse'", transport)
 | 
			
		||||
		return fmt.Errorf("invalid transport type: %s. Must be 'stdio' or 'http'", flag.Mode)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -64,6 +134,8 @@ func newMCPServer(version string) *server.MCPServer {
 | 
			
		||||
	return server.NewMCPServer(
 | 
			
		||||
		"Gitea MCP Server",
 | 
			
		||||
		version,
 | 
			
		||||
		server.WithToolCapabilities(true),
 | 
			
		||||
		server.WithLogging(),
 | 
			
		||||
		server.WithRecovery(),
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										81
									
								
								operation/operation_test.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										81
									
								
								operation/operation_test.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,81 @@
 | 
			
		||||
package operation
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"testing"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func TestParseBearerToken(t *testing.T) {
 | 
			
		||||
	tests := []struct {
 | 
			
		||||
		name      string
 | 
			
		||||
		header    string
 | 
			
		||||
		wantToken string
 | 
			
		||||
		wantOK    bool
 | 
			
		||||
	}{
 | 
			
		||||
		{
 | 
			
		||||
			name:      "valid token",
 | 
			
		||||
			header:    "Bearer validtoken",
 | 
			
		||||
			wantToken: "validtoken",
 | 
			
		||||
			wantOK:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:      "token with spaces trimmed",
 | 
			
		||||
			header:    "Bearer   spacedToken ",
 | 
			
		||||
			wantToken: "spacedToken",
 | 
			
		||||
			wantOK:    true,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:      "lowercase bearer should fail",
 | 
			
		||||
			header:    "bearer lowercase",
 | 
			
		||||
			wantToken: "",
 | 
			
		||||
			wantOK:    false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:      "bearer with no token",
 | 
			
		||||
			header:    "Bearer ",
 | 
			
		||||
			wantToken: "",
 | 
			
		||||
			wantOK:    false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:      "bearer with only spaces",
 | 
			
		||||
			header:    "Bearer     ",
 | 
			
		||||
			wantToken: "",
 | 
			
		||||
			wantOK:    false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:      "missing space after Bearer",
 | 
			
		||||
			header:    "Bearertoken",
 | 
			
		||||
			wantToken: "",
 | 
			
		||||
			wantOK:    false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:      "different auth type",
 | 
			
		||||
			header:    "Basic dXNlcjpwYXNz",
 | 
			
		||||
			wantToken: "",
 | 
			
		||||
			wantOK:    false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:      "empty header",
 | 
			
		||||
			header:    "",
 | 
			
		||||
			wantToken: "",
 | 
			
		||||
			wantOK:    false,
 | 
			
		||||
		},
 | 
			
		||||
		{
 | 
			
		||||
			name:      "token with internal spaces",
 | 
			
		||||
			header:    "Bearer token with spaces",
 | 
			
		||||
			wantToken: "token with spaces",
 | 
			
		||||
			wantOK:    true,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	for _, tt := range tests {
 | 
			
		||||
		t.Run(tt.name, func(t *testing.T) {
 | 
			
		||||
			gotToken, gotOK := parseBearerToken(tt.header)
 | 
			
		||||
			if gotToken != tt.wantToken {
 | 
			
		||||
				t.Errorf("parseBearerToken() token = %q, want %q", gotToken, tt.wantToken)
 | 
			
		||||
			}
 | 
			
		||||
			if gotOK != tt.wantOK {
 | 
			
		||||
				t.Errorf("parseBearerToken() ok = %v, want %v", gotOK, tt.wantOK)
 | 
			
		||||
			}
 | 
			
		||||
		})
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
@@ -7,16 +7,20 @@ import (
 | 
			
		||||
	"gitea.com/gitea/gitea-mcp/pkg/gitea"
 | 
			
		||||
	"gitea.com/gitea/gitea-mcp/pkg/log"
 | 
			
		||||
	"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 (
 | 
			
		||||
	GetPullRequestByIndexToolName = "get_pull_request_by_index"
 | 
			
		||||
	ListRepoPullRequestsToolName  = "list_repo_pull_requests"
 | 
			
		||||
	CreatePullRequestToolName     = "create_pull_request"
 | 
			
		||||
	GetPullRequestByIndexToolName     = "get_pull_request_by_index"
 | 
			
		||||
	ListRepoPullRequestsToolName      = "list_repo_pull_requests"
 | 
			
		||||
	CreatePullRequestToolName         = "create_pull_request"
 | 
			
		||||
	CreatePullRequestReviewerToolName = "create_pull_request_reviewer"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
@@ -50,29 +54,56 @@ var (
 | 
			
		||||
		mcp.WithString("head", mcp.Required(), mcp.Description("pull request head")),
 | 
			
		||||
		mcp.WithString("base", mcp.Required(), mcp.Description("pull request base")),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	CreatePullRequestReviewerTool = mcp.NewTool(
 | 
			
		||||
		CreatePullRequestReviewerToolName,
 | 
			
		||||
		mcp.WithDescription("create pull request reviewer"),
 | 
			
		||||
		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("pull request index")),
 | 
			
		||||
		mcp.WithArray("reviewers", mcp.Description("list of reviewer usernames"), mcp.Items(map[string]interface{}{"type": "string"})),
 | 
			
		||||
		mcp.WithArray("team_reviewers", mcp.Description("list of team reviewer names"), mcp.Items(map[string]interface{}{"type": "string"})),
 | 
			
		||||
	)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func RegisterTool(s *server.MCPServer) {
 | 
			
		||||
	s.AddTool(GetPullRequestByIndexTool, GetPullRequestByIndexFn)
 | 
			
		||||
	s.AddTool(ListRepoPullRequestsTool, ListRepoPullRequestsFn)
 | 
			
		||||
	s.AddTool(CreatePullRequestTool, CreatePullRequestFn)
 | 
			
		||||
func init() {
 | 
			
		||||
	Tool.RegisterRead(server.ServerTool{
 | 
			
		||||
		Tool:    GetPullRequestByIndexTool,
 | 
			
		||||
		Handler: GetPullRequestByIndexFn,
 | 
			
		||||
	})
 | 
			
		||||
	Tool.RegisterRead(server.ServerTool{
 | 
			
		||||
		Tool:    ListRepoPullRequestsTool,
 | 
			
		||||
		Handler: ListRepoPullRequestsFn,
 | 
			
		||||
	})
 | 
			
		||||
	Tool.RegisterWrite(server.ServerTool{
 | 
			
		||||
		Tool:    CreatePullRequestTool,
 | 
			
		||||
		Handler: CreatePullRequestFn,
 | 
			
		||||
	})
 | 
			
		||||
	Tool.RegisterWrite(server.ServerTool{
 | 
			
		||||
		Tool:    CreatePullRequestReviewerTool,
 | 
			
		||||
		Handler: CreatePullRequestReviewerFn,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetPullRequestByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called GetPullRequestByIndexFn")
 | 
			
		||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
			
		||||
	}
 | 
			
		||||
	index, ok := req.Params.Arguments["index"].(float64)
 | 
			
		||||
	index, ok := req.GetArguments()["index"].(float64)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		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 {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("get %v/%v/pr/%v err: %v", owner, repo, int64(index), err))
 | 
			
		||||
	}
 | 
			
		||||
@@ -82,25 +113,25 @@ func GetPullRequestByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp
 | 
			
		||||
 | 
			
		||||
func ListRepoPullRequestsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called ListRepoPullRequests")
 | 
			
		||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
			
		||||
	}
 | 
			
		||||
	state, _ := req.Params.Arguments["state"].(string)
 | 
			
		||||
	sort, ok := req.Params.Arguments["sort"].(string)
 | 
			
		||||
	state, _ := req.GetArguments()["state"].(string)
 | 
			
		||||
	sort, ok := req.GetArguments()["sort"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		sort = "recentupdate"
 | 
			
		||||
	}
 | 
			
		||||
	milestone, _ := req.Params.Arguments["milestone"].(float64)
 | 
			
		||||
	page, ok := req.Params.Arguments["page"].(float64)
 | 
			
		||||
	milestone, _ := req.GetArguments()["milestone"].(float64)
 | 
			
		||||
	page, ok := req.GetArguments()["page"].(float64)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		page = 1
 | 
			
		||||
	}
 | 
			
		||||
	pageSize, ok := req.Params.Arguments["pageSize"].(float64)
 | 
			
		||||
	pageSize, ok := req.GetArguments()["pageSize"].(float64)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		pageSize = 100
 | 
			
		||||
	}
 | 
			
		||||
@@ -113,7 +144,11 @@ func ListRepoPullRequestsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.
 | 
			
		||||
			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 {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("list %v/%v/pull_requests err: %v", owner, repo, err))
 | 
			
		||||
	}
 | 
			
		||||
@@ -123,31 +158,35 @@ func ListRepoPullRequestsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.
 | 
			
		||||
 | 
			
		||||
func CreatePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called CreatePullRequestFn")
 | 
			
		||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
			
		||||
	}
 | 
			
		||||
	title, ok := req.Params.Arguments["title"].(string)
 | 
			
		||||
	title, ok := req.GetArguments()["title"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("title is required"))
 | 
			
		||||
	}
 | 
			
		||||
	body, ok := req.Params.Arguments["body"].(string)
 | 
			
		||||
	body, ok := req.GetArguments()["body"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("body is required"))
 | 
			
		||||
	}
 | 
			
		||||
	head, ok := req.Params.Arguments["head"].(string)
 | 
			
		||||
	head, ok := req.GetArguments()["head"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("head is required"))
 | 
			
		||||
	}
 | 
			
		||||
	base, ok := req.Params.Arguments["base"].(string)
 | 
			
		||||
	base, ok := req.GetArguments()["base"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		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,
 | 
			
		||||
		Body:  body,
 | 
			
		||||
		Head:  head,
 | 
			
		||||
@@ -159,3 +198,65 @@ func CreatePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Cal
 | 
			
		||||
 | 
			
		||||
	return to.TextResult(pr)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func CreatePullRequestReviewerFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called CreatePullRequestReviewerFn")
 | 
			
		||||
	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"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var reviewers []string
 | 
			
		||||
	if reviewersArg, exists := req.GetArguments()["reviewers"]; exists {
 | 
			
		||||
		if reviewersSlice, ok := reviewersArg.([]interface{}); ok {
 | 
			
		||||
			for _, reviewer := range reviewersSlice {
 | 
			
		||||
				if reviewerStr, ok := reviewer.(string); ok {
 | 
			
		||||
					reviewers = append(reviewers, reviewerStr)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var teamReviewers []string
 | 
			
		||||
	if teamReviewersArg, exists := req.GetArguments()["team_reviewers"]; exists {
 | 
			
		||||
		if teamReviewersSlice, ok := teamReviewersArg.([]interface{}); ok {
 | 
			
		||||
			for _, teamReviewer := range teamReviewersSlice {
 | 
			
		||||
				if teamReviewerStr, ok := teamReviewer.(string); ok {
 | 
			
		||||
					teamReviewers = append(teamReviewers, teamReviewerStr)
 | 
			
		||||
				}
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client, err := gitea.ClientFromContext(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err = client.CreateReviewRequests(owner, repo, int64(index), gitea_sdk.PullReviewRequestOptions{
 | 
			
		||||
		Reviewers:     reviewers,
 | 
			
		||||
		TeamReviewers: teamReviewers,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("create review requests for %v/%v/pr/%v err: %v", owner, repo, int64(index), err))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Return a success message instead of the Response object which contains non-serializable functions
 | 
			
		||||
	successMsg := map[string]interface{}{
 | 
			
		||||
		"message":        "Successfully created review requests",
 | 
			
		||||
		"reviewers":      reviewers,
 | 
			
		||||
		"team_reviewers": teamReviewers,
 | 
			
		||||
		"pr_index":       int64(index),
 | 
			
		||||
		"repository":     fmt.Sprintf("%s/%s", owner, repo),
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return to.TextResult(successMsg)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,6 +10,7 @@ import (
 | 
			
		||||
 | 
			
		||||
	gitea_sdk "code.gitea.io/sdk/gitea"
 | 
			
		||||
	"github.com/mark3labs/mcp-go/mcp"
 | 
			
		||||
	"github.com/mark3labs/mcp-go/server"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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) {
 | 
			
		||||
	log.Debugf("Called CreateBranchFn")
 | 
			
		||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
			
		||||
	}
 | 
			
		||||
	branch, ok := req.Params.Arguments["branch"].(string)
 | 
			
		||||
	branch, ok := req.GetArguments()["branch"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		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,
 | 
			
		||||
		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) {
 | 
			
		||||
	log.Debugf("Called DeleteBranchFn")
 | 
			
		||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
			
		||||
	}
 | 
			
		||||
	branch, ok := req.Params.Arguments["branch"].(string)
 | 
			
		||||
	branch, ok := req.GetArguments()["branch"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		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 {
 | 
			
		||||
		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) {
 | 
			
		||||
	log.Debugf("Called ListBranchesFn")
 | 
			
		||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
			
		||||
	}
 | 
			
		||||
@@ -109,7 +133,11 @@ func ListBranchesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
 | 
			
		||||
			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 {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("list branches error: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -10,45 +10,51 @@ import (
 | 
			
		||||
 | 
			
		||||
	gitea_sdk "code.gitea.io/sdk/gitea"
 | 
			
		||||
	"github.com/mark3labs/mcp-go/mcp"
 | 
			
		||||
	"github.com/mark3labs/mcp-go/server"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	ListRepoCommitsToolName = "list_repo_commits"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	ListRepoCommitsTool = mcp.NewTool(
 | 
			
		||||
		ListRepoCommitsToolName,
 | 
			
		||||
		mcp.WithDescription("List repository commits"),
 | 
			
		||||
		mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
			
		||||
		mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
			
		||||
		mcp.WithString("sha", mcp.Description("SHA or branch to start listing commits from")),
 | 
			
		||||
		mcp.WithString("path", mcp.Description("path indicates that only commits that include the path's file/dir should be returned.")),
 | 
			
		||||
		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)),
 | 
			
		||||
	)
 | 
			
		||||
var ListRepoCommitsTool = mcp.NewTool(
 | 
			
		||||
	ListRepoCommitsToolName,
 | 
			
		||||
	mcp.WithDescription("List repository commits"),
 | 
			
		||||
	mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
			
		||||
	mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
			
		||||
	mcp.WithString("sha", mcp.Description("SHA or branch to start listing commits from")),
 | 
			
		||||
	mcp.WithString("path", mcp.Description("path indicates that only commits that include the path's file/dir should be returned.")),
 | 
			
		||||
	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)),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	Tool.RegisterRead(server.ServerTool{
 | 
			
		||||
		Tool:    ListRepoCommitsTool,
 | 
			
		||||
		Handler: ListRepoCommitsFn,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ListRepoCommitsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called ListRepoCommitsFn")
 | 
			
		||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
			
		||||
	}
 | 
			
		||||
	page, ok := req.Params.Arguments["page"].(float64)
 | 
			
		||||
	page, ok := req.GetArguments()["page"].(float64)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		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 {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("page_size is required"))
 | 
			
		||||
	}
 | 
			
		||||
	sha, _ := req.Params.Arguments["sha"].(string)
 | 
			
		||||
	path, _ := req.Params.Arguments["path"].(string)
 | 
			
		||||
	sha, _ := req.GetArguments()["sha"].(string)
 | 
			
		||||
	path, _ := req.GetArguments()["path"].(string)
 | 
			
		||||
	opt := gitea_sdk.ListCommitOptions{
 | 
			
		||||
		ListOptions: gitea_sdk.ListOptions{
 | 
			
		||||
			Page:     int(page),
 | 
			
		||||
@@ -57,7 +63,11 @@ func ListRepoCommitsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
 | 
			
		||||
		SHA:  sha,
 | 
			
		||||
		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 {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("list repo commits err: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,11 @@
 | 
			
		||||
package repo
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"bufio"
 | 
			
		||||
	"bytes"
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/base64"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"gitea.com/gitea/gitea-mcp/pkg/gitea"
 | 
			
		||||
@@ -11,10 +14,12 @@ import (
 | 
			
		||||
 | 
			
		||||
	gitea_sdk "code.gitea.io/sdk/gitea"
 | 
			
		||||
	"github.com/mark3labs/mcp-go/mcp"
 | 
			
		||||
	"github.com/mark3labs/mcp-go/server"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	GetFileToolName    = "get_file_content"
 | 
			
		||||
	GetDirToolName     = "get_dir_content"
 | 
			
		||||
	CreateFileToolName = "create_file"
 | 
			
		||||
	UpdateFileToolName = "update_file"
 | 
			
		||||
	DeleteFileToolName = "delete_file"
 | 
			
		||||
@@ -28,6 +33,16 @@ var (
 | 
			
		||||
		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("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(
 | 
			
		||||
@@ -49,7 +64,7 @@ var (
 | 
			
		||||
		mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
			
		||||
		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("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("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) {
 | 
			
		||||
	log.Debugf("Called GetFileFn")
 | 
			
		||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
			
		||||
	}
 | 
			
		||||
	ref, _ := req.Params.Arguments["ref"].(string)
 | 
			
		||||
	filePath, ok := req.Params.Arguments["filePath"].(string)
 | 
			
		||||
	ref, _ := req.GetArguments()["ref"].(string)
 | 
			
		||||
	filePath, ok := req.GetArguments()["filePath"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		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 {
 | 
			
		||||
		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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func CreateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called CreateFileFn")
 | 
			
		||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
			
		||||
	}
 | 
			
		||||
	filePath, ok := req.Params.Arguments["filePath"].(string)
 | 
			
		||||
	filePath, ok := req.GetArguments()["filePath"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("filePath is required"))
 | 
			
		||||
	}
 | 
			
		||||
	content, _ := req.Params.Arguments["content"].(string)
 | 
			
		||||
	message, _ := req.Params.Arguments["message"].(string)
 | 
			
		||||
	branchName, _ := req.Params.Arguments["branch_name"].(string)
 | 
			
		||||
	content, _ := req.GetArguments()["content"].(string)
 | 
			
		||||
	message, _ := req.GetArguments()["message"].(string)
 | 
			
		||||
	branchName, _ := req.GetArguments()["branch_name"].(string)
 | 
			
		||||
	opt := gitea_sdk.CreateFileOptions{
 | 
			
		||||
		Content: base64.StdEncoding.EncodeToString([]byte(content)),
 | 
			
		||||
		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 {
 | 
			
		||||
		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) {
 | 
			
		||||
	log.Debugf("Called UpdateFileFn")
 | 
			
		||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
			
		||||
	}
 | 
			
		||||
	filePath, ok := req.Params.Arguments["filePath"].(string)
 | 
			
		||||
	filePath, ok := req.GetArguments()["filePath"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("filePath is required"))
 | 
			
		||||
	}
 | 
			
		||||
	sha, ok := req.Params.Arguments["sha"].(string)
 | 
			
		||||
	sha, ok := req.GetArguments()["sha"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("sha is required"))
 | 
			
		||||
	}
 | 
			
		||||
	content, _ := req.Params.Arguments["content"].(string)
 | 
			
		||||
	message, _ := req.Params.Arguments["message"].(string)
 | 
			
		||||
	branchName, _ := req.Params.Arguments["branch_name"].(string)
 | 
			
		||||
	content, _ := req.GetArguments()["content"].(string)
 | 
			
		||||
	message, _ := req.GetArguments()["message"].(string)
 | 
			
		||||
	branchName, _ := req.GetArguments()["branch_name"].(string)
 | 
			
		||||
 | 
			
		||||
	opt := gitea_sdk.UpdateFileOptions{
 | 
			
		||||
		SHA:     sha,
 | 
			
		||||
		Content: content,
 | 
			
		||||
		Content: base64.StdEncoding.EncodeToString([]byte(content)),
 | 
			
		||||
		FileOptions: gitea_sdk.FileOptions{
 | 
			
		||||
			Message:    message,
 | 
			
		||||
			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 {
 | 
			
		||||
		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) {
 | 
			
		||||
	log.Debugf("Called DeleteFileFn")
 | 
			
		||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
			
		||||
	}
 | 
			
		||||
	filePath, ok := req.Params.Arguments["filePath"].(string)
 | 
			
		||||
	filePath, ok := req.GetArguments()["filePath"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("filePath is required"))
 | 
			
		||||
	}
 | 
			
		||||
	message, _ := req.Params.Arguments["message"].(string)
 | 
			
		||||
	branchName, _ := req.Params.Arguments["branch_name"].(string)
 | 
			
		||||
	sha, ok := req.Params.Arguments["sha"].(string)
 | 
			
		||||
	message, _ := req.GetArguments()["message"].(string)
 | 
			
		||||
	branchName, _ := req.GetArguments()["branch_name"].(string)
 | 
			
		||||
	sha, ok := req.GetArguments()["sha"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("sha is required"))
 | 
			
		||||
	}
 | 
			
		||||
@@ -184,7 +303,11 @@ func DeleteFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
 | 
			
		||||
		},
 | 
			
		||||
		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 {
 | 
			
		||||
		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/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 (
 | 
			
		||||
	CreateRepoToolName  = "create_repo"
 | 
			
		||||
	ForkRepoToolName    = "fork_repo"
 | 
			
		||||
@@ -24,7 +27,7 @@ const (
 | 
			
		||||
var (
 | 
			
		||||
	CreateRepoTool = mcp.NewTool(
 | 
			
		||||
		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("description", mcp.Description("Description of the repository to create")),
 | 
			
		||||
		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("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("organization", mcp.Description("Organization name to create repository in (optional - defaults to personal account)")),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	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) {
 | 
			
		||||
	s.AddTool(CreateRepoTool, CreateRepoFn)
 | 
			
		||||
	s.AddTool(ForkRepoTool, ForkRepoFn)
 | 
			
		||||
@@ -70,25 +89,39 @@ func RegisterTool(s *server.MCPServer) {
 | 
			
		||||
	s.AddTool(DeleteBranchTool, DeleteBranchFn)
 | 
			
		||||
	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
 | 
			
		||||
	s.AddTool(ListRepoCommitsTool, ListRepoCommitsFn)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func CreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called CreateRepoFn")
 | 
			
		||||
	name, ok := req.Params.Arguments["name"].(string)
 | 
			
		||||
	name, ok := req.GetArguments()["name"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(errors.New("repository name is required"))
 | 
			
		||||
	}
 | 
			
		||||
	description, _ := req.Params.Arguments["description"].(string)
 | 
			
		||||
	private, _ := req.Params.Arguments["private"].(bool)
 | 
			
		||||
	issueLabels, _ := req.Params.Arguments["issue_labels"].(string)
 | 
			
		||||
	autoInit, _ := req.Params.Arguments["auto_init"].(bool)
 | 
			
		||||
	template, _ := req.Params.Arguments["template"].(bool)
 | 
			
		||||
	gitignores, _ := req.Params.Arguments["gitignores"].(string)
 | 
			
		||||
	license, _ := req.Params.Arguments["license"].(string)
 | 
			
		||||
	readme, _ := req.Params.Arguments["readme"].(string)
 | 
			
		||||
	defaultBranch, _ := req.Params.Arguments["default_branch"].(string)
 | 
			
		||||
	description, _ := req.GetArguments()["description"].(string)
 | 
			
		||||
	private, _ := req.GetArguments()["private"].(bool)
 | 
			
		||||
	issueLabels, _ := req.GetArguments()["issue_labels"].(string)
 | 
			
		||||
	autoInit, _ := req.GetArguments()["auto_init"].(bool)
 | 
			
		||||
	template, _ := req.GetArguments()["template"].(bool)
 | 
			
		||||
	gitignores, _ := req.GetArguments()["gitignores"].(string)
 | 
			
		||||
	license, _ := req.GetArguments()["license"].(string)
 | 
			
		||||
	readme, _ := req.GetArguments()["readme"].(string)
 | 
			
		||||
	defaultBranch, _ := req.GetArguments()["default_branch"].(string)
 | 
			
		||||
	organization, _ := req.GetArguments()["organization"].(string)
 | 
			
		||||
 | 
			
		||||
	opt := gitea_sdk.CreateRepoOption{
 | 
			
		||||
		Name:          name,
 | 
			
		||||
@@ -102,29 +135,42 @@ func CreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
 | 
			
		||||
		Readme:        readme,
 | 
			
		||||
		DefaultBranch: defaultBranch,
 | 
			
		||||
	}
 | 
			
		||||
	repo, _, err := gitea.Client().CreateRepo(opt)
 | 
			
		||||
 | 
			
		||||
	var repo *gitea_sdk.Repository
 | 
			
		||||
	client, err := gitea.ClientFromContext(ctx)
 | 
			
		||||
	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)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ForkRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called ForkRepoFn")
 | 
			
		||||
	user, ok := req.Params.Arguments["user"].(string)
 | 
			
		||||
	user, ok := req.GetArguments()["user"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(errors.New("user name is required"))
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		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)
 | 
			
		||||
	if !ok || organization == "" {
 | 
			
		||||
		organizationPtr = nil
 | 
			
		||||
	}
 | 
			
		||||
	name, ok := req.Params.Arguments["name"].(string)
 | 
			
		||||
	name, ok := req.GetArguments()["name"].(string)
 | 
			
		||||
	namePtr := ptr.To(name)
 | 
			
		||||
	if !ok || name == "" {
 | 
			
		||||
		namePtr = nil
 | 
			
		||||
@@ -133,20 +179,24 @@ func ForkRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResu
 | 
			
		||||
		Organization: organizationPtr,
 | 
			
		||||
		Name:         namePtr,
 | 
			
		||||
	}
 | 
			
		||||
	_, _, err := gitea.Client().CreateFork(user, repo, opt)
 | 
			
		||||
	client, err := gitea.ClientFromContext(ctx)
 | 
			
		||||
	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")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ListMyReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called ListMyReposFn")
 | 
			
		||||
	page, ok := req.Params.Arguments["page"].(float64)
 | 
			
		||||
	page, ok := req.GetArguments()["page"].(float64)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		page = 1
 | 
			
		||||
	}
 | 
			
		||||
	pageSize, ok := req.Params.Arguments["pageSize"].(float64)
 | 
			
		||||
	pageSize, ok := req.GetArguments()["pageSize"].(float64)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		pageSize = 100
 | 
			
		||||
	}
 | 
			
		||||
@@ -156,7 +206,11 @@ func ListMyReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
 | 
			
		||||
			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 {
 | 
			
		||||
		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/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 (
 | 
			
		||||
	SearchUsersToolName    = "search_users"
 | 
			
		||||
	SearchOrgTeamsToolName = "search_org_teams"
 | 
			
		||||
@@ -55,23 +58,32 @@ var (
 | 
			
		||||
	)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func RegisterTool(s *server.MCPServer) {
 | 
			
		||||
	s.AddTool(SearchUsersTool, SearchUsersFn)
 | 
			
		||||
	s.AddTool(SearOrgTeamsTool, SearchOrgTeamsFn)
 | 
			
		||||
	s.AddTool(SearchReposTool, SearchReposFn)
 | 
			
		||||
func init() {
 | 
			
		||||
	Tool.RegisterRead(server.ServerTool{
 | 
			
		||||
		Tool:    SearchUsersTool,
 | 
			
		||||
		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) {
 | 
			
		||||
	log.Debugf("Called SearchUsersFn")
 | 
			
		||||
	keyword, ok := req.Params.Arguments["keyword"].(string)
 | 
			
		||||
	keyword, ok := req.GetArguments()["keyword"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("keyword is required"))
 | 
			
		||||
	}
 | 
			
		||||
	page, ok := req.Params.Arguments["page"].(float64)
 | 
			
		||||
	page, ok := req.GetArguments()["page"].(float64)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		page = 1
 | 
			
		||||
	}
 | 
			
		||||
	pageSize, ok := req.Params.Arguments["pageSize"].(float64)
 | 
			
		||||
	pageSize, ok := req.GetArguments()["pageSize"].(float64)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		pageSize = 100
 | 
			
		||||
	}
 | 
			
		||||
@@ -82,7 +94,11 @@ func SearchUsersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
 | 
			
		||||
			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 {
 | 
			
		||||
		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) {
 | 
			
		||||
	log.Debugf("Called SearchOrgTeamsFn")
 | 
			
		||||
	org, ok := req.Params.Arguments["org"].(string)
 | 
			
		||||
	org, ok := req.GetArguments()["org"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("organization is required"))
 | 
			
		||||
	}
 | 
			
		||||
	query, ok := req.Params.Arguments["query"].(string)
 | 
			
		||||
	query, ok := req.GetArguments()["query"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("query is required"))
 | 
			
		||||
	}
 | 
			
		||||
	includeDescription, _ := req.Params.Arguments["includeDescription"].(bool)
 | 
			
		||||
	page, ok := req.Params.Arguments["page"].(float64)
 | 
			
		||||
	includeDescription, _ := req.GetArguments()["includeDescription"].(bool)
 | 
			
		||||
	page, ok := req.GetArguments()["page"].(float64)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		page = 1
 | 
			
		||||
	}
 | 
			
		||||
	pageSize, ok := req.Params.Arguments["pageSize"].(float64)
 | 
			
		||||
	pageSize, ok := req.GetArguments()["pageSize"].(float64)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		pageSize = 100
 | 
			
		||||
	}
 | 
			
		||||
@@ -116,7 +132,11 @@ func SearchOrgTeamsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
 | 
			
		||||
			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 {
 | 
			
		||||
		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) {
 | 
			
		||||
	log.Debugf("Called SearchReposFn")
 | 
			
		||||
	keyword, ok := req.Params.Arguments["keyword"].(string)
 | 
			
		||||
	keyword, ok := req.GetArguments()["keyword"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("keyword is required"))
 | 
			
		||||
	}
 | 
			
		||||
	keywordIsTopic, _ := req.Params.Arguments["keywordIsTopic"].(bool)
 | 
			
		||||
	keywordInDescription, _ := req.Params.Arguments["keywordInDescription"].(bool)
 | 
			
		||||
	ownerID, _ := req.Params.Arguments["ownerID"].(float64)
 | 
			
		||||
	isPrivate, _ := req.Params.Arguments["isPrivate"].(bool)
 | 
			
		||||
	isArchived, _ := req.Params.Arguments["isArchived"].(bool)
 | 
			
		||||
	sort, _ := req.Params.Arguments["sort"].(string)
 | 
			
		||||
	order, _ := req.Params.Arguments["order"].(string)
 | 
			
		||||
	page, ok := req.Params.Arguments["page"].(float64)
 | 
			
		||||
	keywordIsTopic, _ := req.GetArguments()["keywordIsTopic"].(bool)
 | 
			
		||||
	keywordInDescription, _ := req.GetArguments()["keywordInDescription"].(bool)
 | 
			
		||||
	ownerID, _ := req.GetArguments()["ownerID"].(float64)
 | 
			
		||||
	var pIsPrivate *bool
 | 
			
		||||
	isPrivate, ok := req.GetArguments()["isPrivate"].(bool)
 | 
			
		||||
	if ok {
 | 
			
		||||
		pIsPrivate = ptr.To(isPrivate)
 | 
			
		||||
	}
 | 
			
		||||
	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 {
 | 
			
		||||
		page = 1
 | 
			
		||||
	}
 | 
			
		||||
	pageSize, ok := req.Params.Arguments["pageSize"].(float64)
 | 
			
		||||
	pageSize, ok := req.GetArguments()["pageSize"].(float64)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		pageSize = 100
 | 
			
		||||
	}
 | 
			
		||||
@@ -149,8 +177,8 @@ func SearchReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
 | 
			
		||||
		KeywordIsTopic:       keywordIsTopic,
 | 
			
		||||
		KeywordInDescription: keywordInDescription,
 | 
			
		||||
		OwnerID:              int64(ownerID),
 | 
			
		||||
		IsPrivate:            ptr.To(isPrivate),
 | 
			
		||||
		IsArchived:           ptr.To(isArchived),
 | 
			
		||||
		IsPrivate:            pIsPrivate,
 | 
			
		||||
		IsArchived:           pIsArchived,
 | 
			
		||||
		Sort:                 sort,
 | 
			
		||||
		Order:                order,
 | 
			
		||||
		ListOptions: gitea_sdk.ListOptions{
 | 
			
		||||
@@ -158,7 +186,11 @@ func SearchReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
 | 
			
		||||
			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 {
 | 
			
		||||
		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/log"
 | 
			
		||||
	"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"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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"
 | 
			
		||||
	// 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 (
 | 
			
		||||
	// 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(
 | 
			
		||||
		GetMyUserInfoToolName,
 | 
			
		||||
		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) {
 | 
			
		||||
	s.AddTool(GetMyUserInfoTool, GetUserInfoFn)
 | 
			
		||||
// init registers all MCP tools in Tool at package initialization.
 | 
			
		||||
// 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) {
 | 
			
		||||
	log.Debugf("Called GetUserInfoFn")
 | 
			
		||||
	user, _, err := gitea.Client().GetMyUserInfo()
 | 
			
		||||
	log.Debugf("[User] Called GetUserInfoFn")
 | 
			
		||||
	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 {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("get user info err: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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/log"
 | 
			
		||||
	"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/server"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var Tool = tool.New()
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	GetGiteaMCPServerVersion = "get_gitea_mcp_server_version"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	GetGiteaMCPServerVersionTool = mcp.NewTool(
 | 
			
		||||
		GetGiteaMCPServerVersion,
 | 
			
		||||
		mcp.WithDescription("Get Gitea MCP Server Version"),
 | 
			
		||||
	)
 | 
			
		||||
var GetGiteaMCPServerVersionTool = mcp.NewTool(
 | 
			
		||||
	GetGiteaMCPServerVersion,
 | 
			
		||||
	mcp.WithDescription("Get Gitea MCP Server Version"),
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func RegisterTool(s *server.MCPServer) {
 | 
			
		||||
	s.AddTool(GetGiteaMCPServerVersionTool, GetGiteaMCPServerVersionFn)
 | 
			
		||||
func init() {
 | 
			
		||||
	Tool.RegisterRead(server.ServerTool{
 | 
			
		||||
		Tool:    GetGiteaMCPServerVersionTool,
 | 
			
		||||
		Handler: GetGiteaMCPServerVersionFn,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetGiteaMCPServerVersionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										363
									
								
								operation/wiki/wiki.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										363
									
								
								operation/wiki/wiki.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,363 @@
 | 
			
		||||
package wiki
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"encoding/json"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"io"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"net/url"
 | 
			
		||||
	"strings"
 | 
			
		||||
 | 
			
		||||
	"gitea.com/gitea/gitea-mcp/pkg/flag"
 | 
			
		||||
	"gitea.com/gitea/gitea-mcp/pkg/gitea"
 | 
			
		||||
	"gitea.com/gitea/gitea-mcp/pkg/log"
 | 
			
		||||
	"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/server"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var Tool = tool.New()
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	ListWikiPagesToolName    = "list_wiki_pages"
 | 
			
		||||
	GetWikiPageToolName      = "get_wiki_page"
 | 
			
		||||
	GetWikiRevisionsToolName = "get_wiki_revisions"
 | 
			
		||||
	CreateWikiPageToolName   = "create_wiki_page"
 | 
			
		||||
	UpdateWikiPageToolName   = "update_wiki_page"
 | 
			
		||||
	DeleteWikiPageToolName   = "delete_wiki_page"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	ListWikiPagesTool = mcp.NewTool(
 | 
			
		||||
		ListWikiPagesToolName,
 | 
			
		||||
		mcp.WithDescription("List all wiki pages in a repository"),
 | 
			
		||||
		mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
			
		||||
		mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	GetWikiPageTool = mcp.NewTool(
 | 
			
		||||
		GetWikiPageToolName,
 | 
			
		||||
		mcp.WithDescription("Get a wiki page content and metadata"),
 | 
			
		||||
		mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
			
		||||
		mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
			
		||||
		mcp.WithString("pageName", mcp.Required(), mcp.Description("wiki page name")),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	GetWikiRevisionsTool = mcp.NewTool(
 | 
			
		||||
		GetWikiRevisionsToolName,
 | 
			
		||||
		mcp.WithDescription("Get revisions history of a wiki page"),
 | 
			
		||||
		mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
			
		||||
		mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
			
		||||
		mcp.WithString("pageName", mcp.Required(), mcp.Description("wiki page name")),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	CreateWikiPageTool = mcp.NewTool(
 | 
			
		||||
		CreateWikiPageToolName,
 | 
			
		||||
		mcp.WithDescription("Create a new wiki page"),
 | 
			
		||||
		mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
			
		||||
		mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
			
		||||
		mcp.WithString("title", mcp.Required(), mcp.Description("wiki page title")),
 | 
			
		||||
		mcp.WithString("content_base64", mcp.Required(), mcp.Description("page content, base64 encoded")),
 | 
			
		||||
		mcp.WithString("message", mcp.Description("commit message (optional)")),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	UpdateWikiPageTool = mcp.NewTool(
 | 
			
		||||
		UpdateWikiPageToolName,
 | 
			
		||||
		mcp.WithDescription("Update an existing wiki page"),
 | 
			
		||||
		mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
			
		||||
		mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
			
		||||
		mcp.WithString("pageName", mcp.Required(), mcp.Description("current wiki page name")),
 | 
			
		||||
		mcp.WithString("title", mcp.Description("new page title (optional)")),
 | 
			
		||||
		mcp.WithString("content_base64", mcp.Required(), mcp.Description("page content, base64 encoded")),
 | 
			
		||||
		mcp.WithString("message", mcp.Description("commit message (optional)")),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	DeleteWikiPageTool = mcp.NewTool(
 | 
			
		||||
		DeleteWikiPageToolName,
 | 
			
		||||
		mcp.WithDescription("Delete a wiki page"),
 | 
			
		||||
		mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
			
		||||
		mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
			
		||||
		mcp.WithString("pageName", mcp.Required(), mcp.Description("wiki page name to delete")),
 | 
			
		||||
	)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	Tool.RegisterRead(server.ServerTool{
 | 
			
		||||
		Tool:    ListWikiPagesTool,
 | 
			
		||||
		Handler: ListWikiPagesFn,
 | 
			
		||||
	})
 | 
			
		||||
	Tool.RegisterRead(server.ServerTool{
 | 
			
		||||
		Tool:    GetWikiPageTool,
 | 
			
		||||
		Handler: GetWikiPageFn,
 | 
			
		||||
	})
 | 
			
		||||
	Tool.RegisterRead(server.ServerTool{
 | 
			
		||||
		Tool:    GetWikiRevisionsTool,
 | 
			
		||||
		Handler: GetWikiRevisionsFn,
 | 
			
		||||
	})
 | 
			
		||||
	Tool.RegisterWrite(server.ServerTool{
 | 
			
		||||
		Tool:    CreateWikiPageTool,
 | 
			
		||||
		Handler: CreateWikiPageFn,
 | 
			
		||||
	})
 | 
			
		||||
	Tool.RegisterWrite(server.ServerTool{
 | 
			
		||||
		Tool:    UpdateWikiPageTool,
 | 
			
		||||
		Handler: UpdateWikiPageFn,
 | 
			
		||||
	})
 | 
			
		||||
	Tool.RegisterWrite(server.ServerTool{
 | 
			
		||||
		Tool:    DeleteWikiPageTool,
 | 
			
		||||
		Handler: DeleteWikiPageFn,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ListWikiPagesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called ListWikiPagesFn")
 | 
			
		||||
	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"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client, err := gitea.ClientFromContext(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Use direct HTTP request because SDK does not support yet wikis
 | 
			
		||||
	result, err := makeWikiAPIRequest(ctx, client, "GET", fmt.Sprintf("repos/%s/%s/wiki/pages", url.QueryEscape(owner), url.QueryEscape(repo)), nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("list wiki pages err: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return to.TextResult(result)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called GetWikiPageFn")
 | 
			
		||||
	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"))
 | 
			
		||||
	}
 | 
			
		||||
	pageName, ok := req.GetArguments()["pageName"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("pageName is required"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client, err := gitea.ClientFromContext(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	result, err := makeWikiAPIRequest(ctx, client, "GET", fmt.Sprintf("repos/%s/%s/wiki/page/%s", url.QueryEscape(owner), url.QueryEscape(repo), url.QueryEscape(pageName)), nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("get wiki page err: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return to.TextResult(result)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetWikiRevisionsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called GetWikiRevisionsFn")
 | 
			
		||||
	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"))
 | 
			
		||||
	}
 | 
			
		||||
	pageName, ok := req.GetArguments()["pageName"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("pageName is required"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client, err := gitea.ClientFromContext(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	result, err := makeWikiAPIRequest(ctx, client, "GET", fmt.Sprintf("repos/%s/%s/wiki/revisions/%s", url.QueryEscape(owner), url.QueryEscape(repo), url.QueryEscape(pageName)), nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("get wiki revisions err: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return to.TextResult(result)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func CreateWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called CreateWikiPageFn")
 | 
			
		||||
	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"))
 | 
			
		||||
	}
 | 
			
		||||
	title, ok := req.GetArguments()["title"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("title is required"))
 | 
			
		||||
	}
 | 
			
		||||
	contentBase64, ok := req.GetArguments()["content_base64"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("content_base64 is required"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	message, _ := req.GetArguments()["message"].(string)
 | 
			
		||||
	if message == "" {
 | 
			
		||||
		message = fmt.Sprintf("Create wiki page '%s'", title)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	requestBody := map[string]string{
 | 
			
		||||
		"title":          title,
 | 
			
		||||
		"content_base64": contentBase64,
 | 
			
		||||
		"message":        message,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client, err := gitea.ClientFromContext(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	result, err := makeWikiAPIRequest(ctx, client, "POST", fmt.Sprintf("repos/%s/%s/wiki/new", url.QueryEscape(owner), url.QueryEscape(repo)), requestBody)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("create wiki page err: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return to.TextResult(result)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func UpdateWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called UpdateWikiPageFn")
 | 
			
		||||
	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"))
 | 
			
		||||
	}
 | 
			
		||||
	pageName, ok := req.GetArguments()["pageName"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("pageName is required"))
 | 
			
		||||
	}
 | 
			
		||||
	contentBase64, ok := req.GetArguments()["content_base64"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("content_base64 is required"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	requestBody := map[string]string{
 | 
			
		||||
		"content_base64": contentBase64,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// If title is given, use it. Otherwise, keep current page name
 | 
			
		||||
	if title, ok := req.GetArguments()["title"].(string); ok && title != "" {
 | 
			
		||||
		requestBody["title"] = title
 | 
			
		||||
	} else {
 | 
			
		||||
		// Utiliser pageName comme fallback pour éviter "unnamed"
 | 
			
		||||
		requestBody["title"] = pageName
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if message, ok := req.GetArguments()["message"].(string); ok && message != "" {
 | 
			
		||||
		requestBody["message"] = message
 | 
			
		||||
	} else {
 | 
			
		||||
		requestBody["message"] = fmt.Sprintf("Update wiki page '%s'", pageName)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client, err := gitea.ClientFromContext(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	result, err := makeWikiAPIRequest(ctx, client, "PATCH", fmt.Sprintf("repos/%s/%s/wiki/page/%s", url.QueryEscape(owner), url.QueryEscape(repo), url.QueryEscape(pageName)), requestBody)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("update wiki page err: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return to.TextResult(result)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func DeleteWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called DeleteWikiPageFn")
 | 
			
		||||
	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"))
 | 
			
		||||
	}
 | 
			
		||||
	pageName, ok := req.GetArguments()["pageName"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("pageName is required"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client, err := gitea.ClientFromContext(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err = makeWikiAPIRequest(ctx, client, "DELETE", fmt.Sprintf("repos/%s/%s/wiki/page/%s", url.QueryEscape(owner), url.QueryEscape(repo), url.QueryEscape(pageName)), nil)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("delete wiki page err: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return to.TextResult(map[string]string{"message": "Wiki page deleted successfully"})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
// Helper function to make HTTP requests to Gitea Wiki API
 | 
			
		||||
func makeWikiAPIRequest(ctx context.Context, client interface{}, method, path string, body interface{}) (interface{}, error) {
 | 
			
		||||
	// Use flags to get base URL and token
 | 
			
		||||
	apiURL := fmt.Sprintf("%s/api/v1/%s", flag.Host, path)
 | 
			
		||||
	
 | 
			
		||||
	httpClient := &http.Client{}
 | 
			
		||||
	
 | 
			
		||||
	var reqBody io.Reader
 | 
			
		||||
	if body != nil {
 | 
			
		||||
		bodyBytes, err := json.Marshal(body)
 | 
			
		||||
		if err != nil {
 | 
			
		||||
			return nil, fmt.Errorf("failed to marshal request body: %w", err)
 | 
			
		||||
		}
 | 
			
		||||
		reqBody = strings.NewReader(string(bodyBytes))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	req, err := http.NewRequestWithContext(ctx, method, apiURL, reqBody)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to create request: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	req.Header.Set("Authorization", fmt.Sprintf("token %s", flag.Token))
 | 
			
		||||
	req.Header.Set("Accept", "application/json")
 | 
			
		||||
	if body != nil {
 | 
			
		||||
		req.Header.Set("Content-Type", "application/json")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	resp, err := httpClient.Do(req)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to make request: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	defer resp.Body.Close()
 | 
			
		||||
 | 
			
		||||
	if resp.StatusCode < 200 || resp.StatusCode >= 300 {
 | 
			
		||||
		return nil, fmt.Errorf("API request failed with status %d", resp.StatusCode)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if method == "DELETE" {
 | 
			
		||||
		return map[string]string{"message": "success"}, nil
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	var result interface{}
 | 
			
		||||
	if err := json.NewDecoder(resp.Body).Decode(&result); err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("failed to decode response: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return result, nil
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										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
 | 
			
		||||
	Mode    string
 | 
			
		||||
 | 
			
		||||
	Debug bool
 | 
			
		||||
	Insecure bool
 | 
			
		||||
	ReadOnly bool
 | 
			
		||||
	Debug    bool
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
@@ -1,28 +1,47 @@
 | 
			
		||||
package gitea
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	"gitea.com/gitea/gitea-mcp/pkg/flag"
 | 
			
		||||
	"gitea.com/gitea/gitea-mcp/pkg/log"
 | 
			
		||||
	"context"
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
 | 
			
		||||
	"code.gitea.io/sdk/gitea"
 | 
			
		||||
	mcpContext "gitea.com/gitea/gitea-mcp/pkg/context"
 | 
			
		||||
	"gitea.com/gitea/gitea-mcp/pkg/flag"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	client     *gitea.Client
 | 
			
		||||
	clientOnce sync.Once
 | 
			
		||||
)
 | 
			
		||||
func NewClient(token string) (*gitea.Client, error) {
 | 
			
		||||
	httpClient := &http.Client{
 | 
			
		||||
		Transport: http.DefaultTransport,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
func Client() *gitea.Client {
 | 
			
		||||
	clientOnce.Do(func() {
 | 
			
		||||
		if client == nil {
 | 
			
		||||
			c, err := gitea.NewClient(flag.Host, gitea.SetToken(flag.Token))
 | 
			
		||||
			if err != nil {
 | 
			
		||||
				log.Fatalf("create gitea client err: %v", err)
 | 
			
		||||
			}
 | 
			
		||||
			client = c
 | 
			
		||||
	opts := []gitea.ClientOption{
 | 
			
		||||
		gitea.SetToken(token),
 | 
			
		||||
	}
 | 
			
		||||
	if flag.Insecure {
 | 
			
		||||
		httpClient.Transport.(*http.Transport).TLSClientConfig = &tls.Config{
 | 
			
		||||
			InsecureSkipVerify: true,
 | 
			
		||||
		}
 | 
			
		||||
	})
 | 
			
		||||
	return client
 | 
			
		||||
	}
 | 
			
		||||
	opts = append(opts, gitea.SetHTTPClient(httpClient))
 | 
			
		||||
	if flag.Debug {
 | 
			
		||||
		opts = append(opts, gitea.SetDebugMode())
 | 
			
		||||
	}
 | 
			
		||||
	client, err := gitea.NewClient(flag.Host, opts...)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("create gitea client err: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// Set user agent for the client
 | 
			
		||||
	client.SetUserAgent(fmt.Sprintf("gitea-mcp-server/%s", flag.Version))
 | 
			
		||||
	return client, nil
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ClientFromContext(ctx context.Context) (*gitea.Client, error) {
 | 
			
		||||
	token, ok := ctx.Value(mcpContext.TokenContextKey).(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		token = flag.Token
 | 
			
		||||
	}
 | 
			
		||||
	return NewClient(token)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										106
									
								
								pkg/log/log.go
									
									
									
									
									
								
							
							
						
						
									
										106
									
								
								pkg/log/log.go
									
									
									
									
									
								
							@@ -19,47 +19,55 @@ var (
 | 
			
		||||
 | 
			
		||||
func Default() *zap.Logger {
 | 
			
		||||
	defaultLoggerOnce.Do(func() {
 | 
			
		||||
		if defaultLogger == nil {
 | 
			
		||||
			ec := zap.NewProductionEncoderConfig()
 | 
			
		||||
			ec.EncodeTime = zapcore.TimeEncoderOfLayout(time.DateTime)
 | 
			
		||||
			ec.EncodeLevel = zapcore.CapitalLevelEncoder
 | 
			
		||||
 | 
			
		||||
			var ws zapcore.WriteSyncer
 | 
			
		||||
			var wss []zapcore.WriteSyncer
 | 
			
		||||
 | 
			
		||||
			home, _ := os.UserHomeDir()
 | 
			
		||||
			if home == "" {
 | 
			
		||||
				home = os.TempDir()
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			wss = append(wss, zapcore.AddSync(&lumberjack.Logger{
 | 
			
		||||
				Filename:   fmt.Sprintf("%s/.gitea-mcp/gitea-mcp.log", home),
 | 
			
		||||
				MaxSize:    100,
 | 
			
		||||
				MaxBackups: 10,
 | 
			
		||||
				MaxAge:     30,
 | 
			
		||||
			}))
 | 
			
		||||
 | 
			
		||||
			if flag.Mode == "sse" {
 | 
			
		||||
				wss = append(wss, zapcore.AddSync(os.Stdout))
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			ws = zapcore.NewMultiWriteSyncer(wss...)
 | 
			
		||||
 | 
			
		||||
			enc := zapcore.NewConsoleEncoder(ec)
 | 
			
		||||
			var level zapcore.Level
 | 
			
		||||
			if flag.Debug {
 | 
			
		||||
				level = zapcore.DebugLevel
 | 
			
		||||
			} else {
 | 
			
		||||
				level = zapcore.InfoLevel
 | 
			
		||||
			}
 | 
			
		||||
			core := zapcore.NewCore(enc, ws, level)
 | 
			
		||||
			options := []zap.Option{
 | 
			
		||||
				zap.AddStacktrace(zapcore.DPanicLevel),
 | 
			
		||||
				zap.AddCaller(),
 | 
			
		||||
				zap.AddCallerSkip(1),
 | 
			
		||||
			}
 | 
			
		||||
			defaultLogger = zap.New(core, options...)
 | 
			
		||||
		if defaultLogger != nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ec := zap.NewProductionEncoderConfig()
 | 
			
		||||
		ec.EncodeTime = zapcore.TimeEncoderOfLayout(time.DateTime)
 | 
			
		||||
		ec.EncodeLevel = zapcore.CapitalLevelEncoder
 | 
			
		||||
 | 
			
		||||
		var ws zapcore.WriteSyncer
 | 
			
		||||
		var wss []zapcore.WriteSyncer
 | 
			
		||||
 | 
			
		||||
		home, _ := os.UserHomeDir()
 | 
			
		||||
		if home == "" {
 | 
			
		||||
			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{
 | 
			
		||||
			Filename:   fmt.Sprintf("%s/gitea-mcp.log", logDir),
 | 
			
		||||
			MaxSize:    100,
 | 
			
		||||
			MaxBackups: 10,
 | 
			
		||||
			MaxAge:     30,
 | 
			
		||||
		}))
 | 
			
		||||
 | 
			
		||||
		if flag.Mode == "http" {
 | 
			
		||||
			wss = append(wss, zapcore.AddSync(os.Stdout))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ws = zapcore.NewMultiWriteSyncer(wss...)
 | 
			
		||||
 | 
			
		||||
		enc := zapcore.NewConsoleEncoder(ec)
 | 
			
		||||
		var level zapcore.Level
 | 
			
		||||
		if flag.Debug {
 | 
			
		||||
			level = zapcore.DebugLevel
 | 
			
		||||
		} else {
 | 
			
		||||
			level = zapcore.InfoLevel
 | 
			
		||||
		}
 | 
			
		||||
		core := zapcore.NewCore(enc, ws, level)
 | 
			
		||||
		options := []zap.Option{
 | 
			
		||||
			zap.AddStacktrace(zapcore.DPanicLevel),
 | 
			
		||||
			zap.AddCaller(),
 | 
			
		||||
			zap.AddCallerSkip(1),
 | 
			
		||||
		}
 | 
			
		||||
		defaultLogger = zap.New(core, options...)
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	return defaultLogger
 | 
			
		||||
@@ -71,8 +79,22 @@ func SetDefault(logger *zap.Logger) {
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Logger() *zap.Logger {
 | 
			
		||||
	return defaultLogger
 | 
			
		||||
func New() *Logger {
 | 
			
		||||
	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) {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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