mirror of
				https://gitea.com/gitea/gitea-mcp.git
				synced 2025-11-04 04:11:50 +00:00 
			
		
		
		
	Compare commits
	
		
			34 Commits
		
	
	
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 
						 | 
					de311344cd | ||
| 
						 | 
					d7addd56c4 | ||
| 
						 | 
					dc3e120e97 | ||
| 
						 | 
					f33b04a3df | ||
| 
						 | 
					ba07925969 | ||
| 
						 | 
					5c2ff6dcb2 | ||
| 
						 | 
					feaedaf604 | ||
| 
						 | 
					a601d6b698 | ||
| 
						 | 
					62cb6e7830 | ||
| 
						 | 
					9fff996294 | ||
| 
						 | 
					4c3f5149d8 | ||
| 
						 | 
					eb6b5a8f92 | ||
| 
						 | 
					1d9bdb5b44 | ||
| 
						 | 
					093cddbcb6 | ||
| 
						 | 
					5dbfe21127 | ||
| 
						 | 
					b85a523983 | ||
| 
						 | 
					da08718e24 | ||
| 
						 | 
					44ea8969f4 | ||
| 
						 | 
					94aa8dc572 | ||
| 
						 | 
					05194ffc1c | ||
| 
						 | 
					5c329129f8 | ||
| 
						 | 
					52ccf92761 | ||
| 
						 | 
					061ea86b0b | ||
| 
						 | 
					f14b60fe56 | ||
| 
						 | 
					94782a85b6 | ||
| 
						 | 
					e94dd26b30 | ||
| 
						 | 
					da49bdeb96 | ||
| 
						 | 
					3f61299f72 | ||
| 
						 | 
					5308fbfb2b | ||
| 
						 | 
					a7061f9b64 | ||
| 
						 | 
					f25cc0de8c | ||
| 
						 | 
					417ef26da0 | ||
| 
						 | 
					34ca5d45db | ||
| 
						 | 
					796fd4682d | 
							
								
								
									
										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
 | 
			
		||||
@@ -66,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 }}-server:${{ steps.meta.outputs.REPO_VERSION }}
 | 
			
		||||
            ${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}-server:${{ env.DOCKER_LATEST }}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -1,7 +1,5 @@
 | 
			
		||||
.idea
 | 
			
		||||
.vscode
 | 
			
		||||
 | 
			
		||||
gitea-mcp
 | 
			
		||||
gitea-mcp.exe
 | 
			
		||||
 | 
			
		||||
*.log
 | 
			
		||||
tmp
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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",
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										39
									
								
								Dockerfile
									
									
									
									
									
								
							
							
						
						
									
										39
									
								
								Dockerfile
									
									
									
									
									
								
							@@ -1,39 +1,32 @@
 | 
			
		||||
# syntax=docker/dockerfile:1.4
 | 
			
		||||
 | 
			
		||||
# Build stage
 | 
			
		||||
FROM golang:1.24-bullseye AS builder
 | 
			
		||||
FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder
 | 
			
		||||
 | 
			
		||||
ARG VERSION
 | 
			
		||||
ARG VERSION=dev
 | 
			
		||||
ARG TARGETOS
 | 
			
		||||
ARG TARGETARCH
 | 
			
		||||
 | 
			
		||||
# Set the working directory
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
 | 
			
		||||
# Copy go.mod and go.sum files
 | 
			
		||||
COPY go.mod go.sum ./
 | 
			
		||||
RUN --mount=type=cache,target=/go/pkg/mod \
 | 
			
		||||
    go mod download
 | 
			
		||||
 | 
			
		||||
# Download dependencies
 | 
			
		||||
RUN go mod download
 | 
			
		||||
 | 
			
		||||
# Copy the source code
 | 
			
		||||
COPY . .
 | 
			
		||||
 | 
			
		||||
RUN CGO_ENABLED=0 go build -ldflags="-s -w -X main.Version=${VERSION}" -o gitea-mcp
 | 
			
		||||
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 debian:bullseye-slim
 | 
			
		||||
 | 
			
		||||
ENV GITEA_MODE=stdio
 | 
			
		||||
FROM gcr.io/distroless/static-debian12:nonroot
 | 
			
		||||
 | 
			
		||||
WORKDIR /app
 | 
			
		||||
COPY --from=builder --chown=nonroot:nonroot /app/gitea-mcp .
 | 
			
		||||
 | 
			
		||||
# Install ca-certificates for HTTPS requests
 | 
			
		||||
RUN apt-get update && \
 | 
			
		||||
    apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
 | 
			
		||||
USER nonroot:nonroot
 | 
			
		||||
 | 
			
		||||
# Create a non-root user
 | 
			
		||||
RUN useradd -r -u 1000 -m gitea-mcp
 | 
			
		||||
 | 
			
		||||
COPY --from=builder --chown=1000:1000 /app/gitea-mcp .
 | 
			
		||||
 | 
			
		||||
# Use the non-root user
 | 
			
		||||
USER gitea-mcp
 | 
			
		||||
LABEL org.opencontainers.image.version="${VERSION}"
 | 
			
		||||
 | 
			
		||||
CMD ["/app/gitea-mcp"]
 | 
			
		||||
							
								
								
									
										45
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										45
									
								
								README.md
									
									
									
									
									
								
							@@ -6,6 +6,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}}) [](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?
 | 
			
		||||
 | 
			
		||||
Gitea is a community-managed lightweight code hosting solution written in Go. It is published under the MIT license. Gitea provides Git hosting including a repository viewer, issue tracking, pull requests, and more.
 | 
			
		||||
@@ -38,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",
 | 
			
		||||
@@ -59,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
 | 
			
		||||
 | 
			
		||||
@@ -123,7 +139,25 @@ To configure the MCP server for Gitea, add the following to your MCP configurati
 | 
			
		||||
{
 | 
			
		||||
  "mcpServers": {
 | 
			
		||||
    "gitea": {
 | 
			
		||||
      "url": "http://localhost:8080/sse"
 | 
			
		||||
      "url": "http://localhost:8080/sse",
 | 
			
		||||
      "headers": {
 | 
			
		||||
        "Authorization": "Bearer <your personal access token>"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
- **http mode**
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
{
 | 
			
		||||
  "mcpServers": {
 | 
			
		||||
    "gitea": {
 | 
			
		||||
      "url": "http://localhost:8080/mcp",
 | 
			
		||||
      "headers": {
 | 
			
		||||
        "Authorization": "Bearer <your personal access token>"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -146,7 +180,7 @@ 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       |
 | 
			
		||||
|        get_user_orgs         |     User     | Get organizations associated with the authenticated user |
 | 
			
		||||
|         create_repo          |  Repository  |                 Create a new repository                  |
 | 
			
		||||
@@ -166,6 +200,7 @@ The Gitea MCP Server supports the following tools:
 | 
			
		||||
|          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                       |
 | 
			
		||||
@@ -174,6 +209,8 @@ The Gitea MCP Server supports the following tools:
 | 
			
		||||
|         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                 |
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,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}}) [](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}&quality=insiders)
 | 
			
		||||
 | 
			
		||||
## 目录
 | 
			
		||||
 | 
			
		||||
- [Gitea MCP 服务器](#gitea-mcp-服务器)
 | 
			
		||||
  - [目录](#目录)
 | 
			
		||||
  - [什么是 Gitea?](#什么是-gitea)
 | 
			
		||||
  - [什么是 MCP?](#什么是-mcp)
 | 
			
		||||
  - [🚧 安装](#-安装)
 | 
			
		||||
    - [在 VS Code 中使用](#在-vs-code-中使用)
 | 
			
		||||
    - [📥 下载官方 Gitea MCP 二进制版本](#-下载官方-gitea-mcp-二进制版本)
 | 
			
		||||
    - [🔧 从源代码构建](#-从源代码构建)
 | 
			
		||||
    - [📁 添加到 PATH](#-添加到-path)
 | 
			
		||||
  - [🚀 使用](#-使用)
 | 
			
		||||
  - [✅ 可用工具](#-可用工具)
 | 
			
		||||
  - [🐛 调试](#-调试)
 | 
			
		||||
  - [🛠 疑难排解](#-疑难排解)
 | 
			
		||||
 | 
			
		||||
## 什么是 Gitea?
 | 
			
		||||
 | 
			
		||||
Gitea 是一个由社区管理的轻量级代码托管解决方案,使用 Go 语言编写。它以 MIT 许可证发布。Gitea 提供 Git 托管,包括仓库查看器、问题追踪、拉取请求等功能。
 | 
			
		||||
@@ -38,7 +54,7 @@ Model Context Protocol (MCP) 是一种协议,允许通过聊天界面整合各
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "servers": {
 | 
			
		||||
      "github": {
 | 
			
		||||
      "gitea-mcp": {
 | 
			
		||||
        "command": "docker",
 | 
			
		||||
        "args": [
 | 
			
		||||
          "run",
 | 
			
		||||
@@ -57,9 +73,9 @@ Model Context Protocol (MCP) 是一种协议,允许通过聊天界面整合各
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### 📥 下载官方二进制版本
 | 
			
		||||
### 📥 下载官方 Gitea MCP 二进制版本
 | 
			
		||||
 | 
			
		||||
您可以从[这里](https://gitea.com/gitea/gitea-mcp/releases)下载官方版本。
 | 
			
		||||
您可以从[官方 Gitea MCP 二进制版本](https://gitea.com/gitea/gitea-mcp/releases)下载官方版本。
 | 
			
		||||
 | 
			
		||||
### 🔧 从源代码构建
 | 
			
		||||
 | 
			
		||||
@@ -123,7 +139,25 @@ cp gitea-mcp /usr/local/bin/
 | 
			
		||||
{
 | 
			
		||||
  "mcpServers": {
 | 
			
		||||
    "gitea": {
 | 
			
		||||
      "url": "http://localhost:8080/sse"
 | 
			
		||||
      "url": "http://localhost:8080/sse",
 | 
			
		||||
      "headers": {
 | 
			
		||||
        "Authorization": "Bearer <your personal access token>"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
- **http 模式**
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
{
 | 
			
		||||
  "mcpServers": {
 | 
			
		||||
    "gitea": {
 | 
			
		||||
      "url": "http://localhost:8080/mcp",
 | 
			
		||||
      "headers": {
 | 
			
		||||
        "Authorization": "Bearer <your personal access token>"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
@@ -148,6 +182,7 @@ Gitea MCP 服务器支持以下工具:
 | 
			
		||||
|             工具             |   范围   |             描述             |
 | 
			
		||||
| :--------------------------: | :------: | :--------------------------: |
 | 
			
		||||
|       get_my_user_info       |   用户   |     获取已认证用户的信息     |
 | 
			
		||||
|        get_user_orgs         |   用户   |   获取已认证用户关联的组织   |
 | 
			
		||||
|         create_repo          |   仓库   |        创建一个新仓库        |
 | 
			
		||||
|          fork_repo           |   仓库   |         复刻一个仓库         |
 | 
			
		||||
|        list_my_repos         |   仓库   | 列出已认证用户拥有的所有仓库 |
 | 
			
		||||
@@ -165,6 +200,7 @@ Gitea MCP 服务器支持以下工具:
 | 
			
		||||
|          list_tags           |   标签   |         列出所有标签         |
 | 
			
		||||
|      list_repo_commits       |   提交   |     列出仓库中的所有提交     |
 | 
			
		||||
|       get_file_content       |   文件   |    获取文件的内容和元数据    |
 | 
			
		||||
|        get_dir_content       |   文件   |      获取目录的内容列表      |
 | 
			
		||||
|         create_file          |   文件   |        创建一个新文件        |
 | 
			
		||||
|         update_file          |   文件   |         更新现有文件         |
 | 
			
		||||
|         delete_file          |   文件   |         删除一个文件         |
 | 
			
		||||
@@ -173,6 +209,8 @@ Gitea MCP 服务器支持以下工具:
 | 
			
		||||
|         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      | 拉取请求 |      创建一个新拉取请求      |
 | 
			
		||||
 
 | 
			
		||||
@@ -6,6 +6,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}}) [](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}&quality=insiders)
 | 
			
		||||
 | 
			
		||||
## 目錄
 | 
			
		||||
 | 
			
		||||
- [Gitea MCP 伺服器](#gitea-mcp-伺服器)
 | 
			
		||||
  - [目錄](#目錄)
 | 
			
		||||
  - [什麼是 Gitea?](#什麼是-gitea)
 | 
			
		||||
  - [什麼是 MCP?](#什麼是-mcp)
 | 
			
		||||
  - [🚧 安裝](#-安裝)
 | 
			
		||||
    - [在 VS Code 中使用](#在-vs-code-中使用)
 | 
			
		||||
    - [📥 下載官方 Gitea MCP 二進位版本](#-下載官方-gitea-mcp-二進位版本)
 | 
			
		||||
    - [🔧 從源代碼構建](#-從源代碼構建)
 | 
			
		||||
    - [📁 添加到 PATH](#-添加到-path)
 | 
			
		||||
  - [🚀 使用](#-使用)
 | 
			
		||||
  - [✅ 可用工具](#-可用工具)
 | 
			
		||||
  - [🐛 調試](#-調試)
 | 
			
		||||
  - [🛠 疑難排解](#-疑難排解)
 | 
			
		||||
 | 
			
		||||
## 什麼是 Gitea?
 | 
			
		||||
 | 
			
		||||
Gitea 是一個由社群管理的輕量級代碼託管解決方案,使用 Go 語言編寫。它以 MIT 許可證發布。Gitea 提供 Git 託管,包括倉庫查看器、問題追蹤、拉取請求等功能。
 | 
			
		||||
@@ -38,7 +54,7 @@ Model Context Protocol (MCP) 是一種協議,允許通過聊天界面整合各
 | 
			
		||||
      }
 | 
			
		||||
    ],
 | 
			
		||||
    "servers": {
 | 
			
		||||
      "github": {
 | 
			
		||||
      "gitea-mcp": {
 | 
			
		||||
        "command": "docker",
 | 
			
		||||
        "args": [
 | 
			
		||||
          "run",
 | 
			
		||||
@@ -57,9 +73,9 @@ Model Context Protocol (MCP) 是一種協議,允許通過聊天界面整合各
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### 📥 下載官方二進制版本
 | 
			
		||||
### 📥 下載官方 Gitea MCP 二進位版本
 | 
			
		||||
 | 
			
		||||
您可以從[這裡](https://gitea.com/gitea/gitea-mcp/releases)下載官方版本。
 | 
			
		||||
您可以從[官方 Gitea MCP 二進位版本](https://gitea.com/gitea/gitea-mcp/releases)下載官方版本。
 | 
			
		||||
 | 
			
		||||
### 🔧 從源代碼構建
 | 
			
		||||
 | 
			
		||||
@@ -123,17 +139,35 @@ cp gitea-mcp /usr/local/bin/
 | 
			
		||||
{
 | 
			
		||||
  "mcpServers": {
 | 
			
		||||
    "gitea": {
 | 
			
		||||
      "url": "http://localhost:8080/sse"
 | 
			
		||||
      "url": "http://localhost:8080/sse",
 | 
			
		||||
      "headers": {
 | 
			
		||||
        "Authorization": "Bearer <your personal access token>"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
**默認日誌路徑**: `$HOME/.gitea-mcp/gitea-mcp.log`
 | 
			
		||||
- **http 模式**
 | 
			
		||||
 | 
			
		||||
```json
 | 
			
		||||
{
 | 
			
		||||
  "mcpServers": {
 | 
			
		||||
    "gitea": {
 | 
			
		||||
      "url": "http://localhost:8080/mcp",
 | 
			
		||||
      "headers": {
 | 
			
		||||
        "Authorization": "Bearer <your personal access token>"
 | 
			
		||||
      }
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
**預設日誌路徑**: `$HOME/.gitea-mcp/gitea-mcp.log`
 | 
			
		||||
 | 
			
		||||
> [!注意]
 | 
			
		||||
> 您可以通過命令行參數或環境變量提供您的 Gitea 主機和訪問令牌。
 | 
			
		||||
> 命令行參數具有最高優先級
 | 
			
		||||
> 您可以通過命令列參數或環境變數提供您的 Gitea 主機和訪問令牌。
 | 
			
		||||
> 命令列參數具有最高優先權
 | 
			
		||||
 | 
			
		||||
一切設置完成後,請嘗試在您的 MCP 兼容聊天框中輸入以下內容:
 | 
			
		||||
 | 
			
		||||
@@ -144,9 +178,11 @@ cp gitea-mcp /usr/local/bin/
 | 
			
		||||
## ✅ 可用工具
 | 
			
		||||
 | 
			
		||||
Gitea MCP 伺服器支持以下工具:
 | 
			
		||||
 | 
			
		||||
|             工具             |   範圍   |             描述             |
 | 
			
		||||
| :--------------------------: | :------: | :--------------------------: |
 | 
			
		||||
|       get_my_user_info       |   用戶   |     獲取已認證用戶的信息     |
 | 
			
		||||
|        get_user_orgs         |   用戶   |    取得已認證用戶所屬組織    |
 | 
			
		||||
|         create_repo          |   倉庫   |        創建一個新倉庫        |
 | 
			
		||||
|          fork_repo           |   倉庫   |         復刻一個倉庫         |
 | 
			
		||||
|        list_my_repos         |   倉庫   | 列出已認證用戶擁有的所有倉庫 |
 | 
			
		||||
@@ -164,6 +200,7 @@ Gitea MCP 伺服器支持以下工具:
 | 
			
		||||
|          list_tags           |   標籤   |         列出所有標籤         |
 | 
			
		||||
|      list_repo_commits       |   提交   |     列出倉庫中的所有提交     |
 | 
			
		||||
|       get_file_content       |   文件   |    獲取文件的內容和元數據    |
 | 
			
		||||
|        get_dir_content       |   文件   |      獲取目錄的內容列表      |
 | 
			
		||||
|         create_file          |   文件   |        創建一個新文件        |
 | 
			
		||||
|         update_file          |   文件   |         更新現有文件         |
 | 
			
		||||
|         delete_file          |   文件   |         刪除一個文件         |
 | 
			
		||||
@@ -172,6 +209,8 @@ Gitea MCP 伺服器支持以下工具:
 | 
			
		||||
|         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      | 拉取請求 |      創建一個新拉取請求      |
 | 
			
		||||
@@ -182,7 +221,7 @@ Gitea MCP 伺服器支持以下工具:
 | 
			
		||||
 | 
			
		||||
## 🐛 調試
 | 
			
		||||
 | 
			
		||||
要啟用調試模式,請在使用 sse 模式運行 Gitea MCP 伺服器時添加 `-d` 標誌:
 | 
			
		||||
要啟用調試模式,請在使用 sse 模式運行 Gitea MCP 伺服器時添加 `-d` 旗標:
 | 
			
		||||
 | 
			
		||||
```sh
 | 
			
		||||
./gitea-mcp -t sse [--port 8080] --token <your personal access token> -d
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										10
									
								
								cmd/cmd.go
									
									
									
									
									
								
							
							
						
						
									
										10
									
								
								cmd/cmd.go
									
									
									
									
									
								
							@@ -21,13 +21,13 @@ func init() {
 | 
			
		||||
		&flagPkg.Mode,
 | 
			
		||||
		"t",
 | 
			
		||||
		"stdio",
 | 
			
		||||
		"Transport type (stdio or sse)",
 | 
			
		||||
		"Transport type (stdio, sse or http)",
 | 
			
		||||
	)
 | 
			
		||||
	flag.StringVar(
 | 
			
		||||
		&flagPkg.Mode,
 | 
			
		||||
		"transport",
 | 
			
		||||
		"stdio",
 | 
			
		||||
		"Transport type (stdio or sse)",
 | 
			
		||||
		"Transport type (stdio, sse or http)",
 | 
			
		||||
	)
 | 
			
		||||
	flag.StringVar(
 | 
			
		||||
		&host,
 | 
			
		||||
@@ -39,7 +39,7 @@ func init() {
 | 
			
		||||
		&port,
 | 
			
		||||
		"port",
 | 
			
		||||
		8080,
 | 
			
		||||
		"sse port",
 | 
			
		||||
		"see or http port",
 | 
			
		||||
	)
 | 
			
		||||
	flag.StringVar(
 | 
			
		||||
		&token,
 | 
			
		||||
@@ -80,8 +80,8 @@ func init() {
 | 
			
		||||
		flagPkg.Token = os.Getenv("GITEA_ACCESS_TOKEN")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if os.Getenv("GITEA_MODE") != "" {
 | 
			
		||||
		flagPkg.Mode = os.Getenv("GITEA_MODE")
 | 
			
		||||
	if os.Getenv("MCP_MODE") != "" {
 | 
			
		||||
		flagPkg.Mode = os.Getenv("MCP_MODE")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if os.Getenv("GITEA_READONLY") == "true" {
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										16
									
								
								go.mod
									
									
									
									
									
								
							
							
						
						
									
										16
									
								
								go.mod
									
									
									
									
									
								
							@@ -4,20 +4,26 @@ go 1.24.0
 | 
			
		||||
 | 
			
		||||
require (
 | 
			
		||||
	code.gitea.io/sdk/gitea v0.21.0
 | 
			
		||||
	github.com/mark3labs/mcp-go v0.22.0
 | 
			
		||||
	github.com/mark3labs/mcp-go v0.36.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/spf13/cast v1.7.1 // indirect
 | 
			
		||||
	github.com/invopop/jsonschema v0.13.0 // indirect
 | 
			
		||||
	github.com/mailru/easyjson v0.7.7 // indirect
 | 
			
		||||
	github.com/spf13/cast v1.9.2 // indirect
 | 
			
		||||
	github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect
 | 
			
		||||
	github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
 | 
			
		||||
	go.uber.org/multierr v1.11.0 // indirect
 | 
			
		||||
	golang.org/x/crypto v0.37.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.32.0 // indirect
 | 
			
		||||
	golang.org/x/crypto v0.40.0 // indirect
 | 
			
		||||
	golang.org/x/sys v0.34.0 // indirect
 | 
			
		||||
	gopkg.in/yaml.v3 v3.0.1 // indirect
 | 
			
		||||
)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										37
									
								
								go.sum
									
									
									
									
									
								
							
							
						
						
									
										37
									
								
								go.sum
									
									
									
									
									
								
							@@ -1,7 +1,11 @@
 | 
			
		||||
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=
 | 
			
		||||
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=
 | 
			
		||||
@@ -16,20 +20,27 @@ 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/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
 | 
			
		||||
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
 | 
			
		||||
github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y=
 | 
			
		||||
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
 | 
			
		||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
 | 
			
		||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
 | 
			
		||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
 | 
			
		||||
github.com/mark3labs/mcp-go v0.22.0 h1:cCEBWi4Yy9Kio+OW1hWIyi4WLsSr+RBBK6FI5tj+b7I=
 | 
			
		||||
github.com/mark3labs/mcp-go v0.22.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
 | 
			
		||||
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
 | 
			
		||||
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
 | 
			
		||||
github.com/mark3labs/mcp-go v0.36.0 h1:rIZaijrRYPeSbJG8/qNDe0hWlGrCJ7FWHNMz2SQpTis=
 | 
			
		||||
github.com/mark3labs/mcp-go v0.36.0/go.mod h1:T7tUa2jO6MavG+3P25Oy/jR7iCeJPHImCZHRymCn39g=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
 | 
			
		||||
github.com/pmezard/go-difflib v1.0.0/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.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y=
 | 
			
		||||
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
 | 
			
		||||
github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
 | 
			
		||||
github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
 | 
			
		||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
 | 
			
		||||
github.com/stretchr/testify v1.10.0/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=
 | 
			
		||||
@@ -41,21 +52,23 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
 | 
			
		||||
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
 | 
			
		||||
golang.org/x/crypto v0.0.0-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.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE=
 | 
			
		||||
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc=
 | 
			
		||||
golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
 | 
			
		||||
golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
 | 
			
		||||
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
 | 
			
		||||
golang.org/x/net v0.0.0-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.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
 | 
			
		||||
golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
 | 
			
		||||
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
 | 
			
		||||
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o=
 | 
			
		||||
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw=
 | 
			
		||||
golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
 | 
			
		||||
golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
 | 
			
		||||
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
 | 
			
		||||
golang.org/x/text v0.3.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=
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,8 @@ const (
 | 
			
		||||
	CreateIssueToolName             = "create_issue"
 | 
			
		||||
	CreateIssueCommentToolName      = "create_issue_comment"
 | 
			
		||||
	EditIssueToolName               = "edit_issue"
 | 
			
		||||
	EditIssueCommentToolName        = "edit_issue_comment"
 | 
			
		||||
	GetIssueCommentsByIndexToolName = "get_issue_comments_by_index"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
@@ -52,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"),
 | 
			
		||||
@@ -60,6 +63,7 @@ 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"),
 | 
			
		||||
@@ -72,6 +76,23 @@ var (
 | 
			
		||||
		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 init() {
 | 
			
		||||
@@ -95,23 +116,35 @@ func init() {
 | 
			
		||||
		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))
 | 
			
		||||
	}
 | 
			
		||||
@@ -121,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
 | 
			
		||||
	}
 | 
			
		||||
@@ -148,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))
 | 
			
		||||
	}
 | 
			
		||||
@@ -157,23 +194,27 @@ 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,
 | 
			
		||||
	})
 | 
			
		||||
@@ -186,26 +227,30 @@ 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("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))
 | 
			
		||||
	}
 | 
			
		||||
@@ -215,46 +260,110 @@ func CreateIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca
 | 
			
		||||
 | 
			
		||||
func EditIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called EditIssueFn")
 | 
			
		||||
	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"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	opt := gitea_sdk.EditIssueOption{}
 | 
			
		||||
 | 
			
		||||
	title, ok := req.Params.Arguments["title"].(string)
 | 
			
		||||
	title, ok := req.GetArguments()["title"].(string)
 | 
			
		||||
	if ok {
 | 
			
		||||
		opt.Title = title
 | 
			
		||||
	}
 | 
			
		||||
	body, ok := req.Params.Arguments["body"].(string)
 | 
			
		||||
	body, ok := req.GetArguments()["body"].(string)
 | 
			
		||||
	if ok {
 | 
			
		||||
		opt.Body = ptr.To(body)
 | 
			
		||||
	}
 | 
			
		||||
	assignees, ok := req.Params.Arguments["assignees"].([]string)
 | 
			
		||||
	assignees, ok := req.GetArguments()["assignees"].([]string)
 | 
			
		||||
	if ok {
 | 
			
		||||
		opt.Assignees = assignees
 | 
			
		||||
	}
 | 
			
		||||
	milestone, ok := req.Params.Arguments["milestone"].(float64)
 | 
			
		||||
	milestone, ok := req.GetArguments()["milestone"].(float64)
 | 
			
		||||
	if ok {
 | 
			
		||||
		opt.Milestone = ptr.To(int64(milestone))
 | 
			
		||||
	}
 | 
			
		||||
	state, ok := req.Params.Arguments["state"].(string)
 | 
			
		||||
	state, ok := req.GetArguments()["state"].(string)
 | 
			
		||||
	if ok {
 | 
			
		||||
		opt.State = ptr.To(gitea_sdk.StateType(state))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	issue, _, err := gitea.Client().EditIssue(owner, repo, int64(index), opt)
 | 
			
		||||
	client, err := gitea.ClientFromContext(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
	issue, _, err := client.EditIssue(owner, repo, int64(index), opt)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("edit %v/%v/issue/%v err: %v", owner, repo, int64(index), err))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return to.TextResult(issue)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func EditIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called EditIssueCommentFn")
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
			
		||||
	}
 | 
			
		||||
	commentID, ok := req.GetArguments()["commentID"].(float64)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("comment ID is required"))
 | 
			
		||||
	}
 | 
			
		||||
	body, ok := req.GetArguments()["body"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("body is required"))
 | 
			
		||||
	}
 | 
			
		||||
	opt := gitea_sdk.EditIssueCommentOption{
 | 
			
		||||
		Body: body,
 | 
			
		||||
	}
 | 
			
		||||
	client, err := gitea.ClientFromContext(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
	issueComment, _, err := client.EditIssueComment(owner, repo, int64(commentID), opt)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("edit %v/%v/issues/comments/%v err: %v", owner, repo, int64(commentID), err))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return to.TextResult(issueComment)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetIssueCommentsByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called GetIssueCommentsByIndexFn")
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
			
		||||
	}
 | 
			
		||||
	index, ok := req.GetArguments()["index"].(float64)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("index is required"))
 | 
			
		||||
	}
 | 
			
		||||
	opt := gitea_sdk.ListIssueCommentOptions{}
 | 
			
		||||
	client, err := gitea.ClientFromContext(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
	issue, _, err := client.ListIssueComments(owner, repo, int64(index), opt)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("get %v/%v/issues/%v/comments err: %v", owner, repo, int64(index), err))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return to.TextResult(issue)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										454
									
								
								operation/label/label.go
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										454
									
								
								operation/label/label.go
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,454 @@
 | 
			
		||||
package label
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
 | 
			
		||||
	"gitea.com/gitea/gitea-mcp/pkg/gitea"
 | 
			
		||||
	"gitea.com/gitea/gitea-mcp/pkg/log"
 | 
			
		||||
	"gitea.com/gitea/gitea-mcp/pkg/ptr"
 | 
			
		||||
	"gitea.com/gitea/gitea-mcp/pkg/to"
 | 
			
		||||
	"gitea.com/gitea/gitea-mcp/pkg/tool"
 | 
			
		||||
 | 
			
		||||
	gitea_sdk "code.gitea.io/sdk/gitea"
 | 
			
		||||
	"github.com/mark3labs/mcp-go/mcp"
 | 
			
		||||
	"github.com/mark3labs/mcp-go/server"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var Tool = tool.New()
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	ListRepoLabelsToolName     = "list_repo_labels"
 | 
			
		||||
	GetRepoLabelToolName       = "get_repo_label"
 | 
			
		||||
	CreateRepoLabelToolName    = "create_repo_label"
 | 
			
		||||
	EditRepoLabelToolName      = "edit_repo_label"
 | 
			
		||||
	DeleteRepoLabelToolName    = "delete_repo_label"
 | 
			
		||||
	AddIssueLabelsToolName     = "add_issue_labels"
 | 
			
		||||
	ReplaceIssueLabelsToolName = "replace_issue_labels"
 | 
			
		||||
	ClearIssueLabelsToolName   = "clear_issue_labels"
 | 
			
		||||
	RemoveIssueLabelToolName   = "remove_issue_label"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	ListRepoLabelsTool = mcp.NewTool(
 | 
			
		||||
		ListRepoLabelsToolName,
 | 
			
		||||
		mcp.WithDescription("Lists all labels for a given repository"),
 | 
			
		||||
		mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
			
		||||
		mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
			
		||||
		mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
 | 
			
		||||
		mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	GetRepoLabelTool = mcp.NewTool(
 | 
			
		||||
		GetRepoLabelToolName,
 | 
			
		||||
		mcp.WithDescription("Gets a single label by its ID for a repository"),
 | 
			
		||||
		mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
			
		||||
		mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
			
		||||
		mcp.WithNumber("id", mcp.Required(), mcp.Description("label ID")),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	CreateRepoLabelTool = mcp.NewTool(
 | 
			
		||||
		CreateRepoLabelToolName,
 | 
			
		||||
		mcp.WithDescription("Creates a new label for a repository"),
 | 
			
		||||
		mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
			
		||||
		mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
			
		||||
		mcp.WithString("name", mcp.Required(), mcp.Description("label name")),
 | 
			
		||||
		mcp.WithString("color", mcp.Required(), mcp.Description("label color (hex code, e.g., #RRGGBB)")),
 | 
			
		||||
		mcp.WithString("description", mcp.Description("label description")),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	EditRepoLabelTool = mcp.NewTool(
 | 
			
		||||
		EditRepoLabelToolName,
 | 
			
		||||
		mcp.WithDescription("Edits an existing label in a repository"),
 | 
			
		||||
		mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
			
		||||
		mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
			
		||||
		mcp.WithNumber("id", mcp.Required(), mcp.Description("label ID")),
 | 
			
		||||
		mcp.WithString("name", mcp.Description("new label name")),
 | 
			
		||||
		mcp.WithString("color", mcp.Description("new label color (hex code, e.g., #RRGGBB)")),
 | 
			
		||||
		mcp.WithString("description", mcp.Description("new label description")),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	DeleteRepoLabelTool = mcp.NewTool(
 | 
			
		||||
		DeleteRepoLabelToolName,
 | 
			
		||||
		mcp.WithDescription("Deletes a label from a repository"),
 | 
			
		||||
		mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
			
		||||
		mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
			
		||||
		mcp.WithNumber("id", mcp.Required(), mcp.Description("label ID")),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	AddIssueLabelsTool = mcp.NewTool(
 | 
			
		||||
		AddIssueLabelsToolName,
 | 
			
		||||
		mcp.WithDescription("Adds one or more labels to an issue"),
 | 
			
		||||
		mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
			
		||||
		mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
			
		||||
		mcp.WithNumber("index", mcp.Required(), mcp.Description("issue index")),
 | 
			
		||||
		mcp.WithArray("labels", mcp.Required(), mcp.Description("array of label IDs to add"), mcp.Items(map[string]interface{}{"type": "number"})),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	ReplaceIssueLabelsTool = mcp.NewTool(
 | 
			
		||||
		ReplaceIssueLabelsToolName,
 | 
			
		||||
		mcp.WithDescription("Replaces all labels on an issue"),
 | 
			
		||||
		mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
			
		||||
		mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
			
		||||
		mcp.WithNumber("index", mcp.Required(), mcp.Description("issue index")),
 | 
			
		||||
		mcp.WithArray("labels", mcp.Required(), mcp.Description("array of label IDs to replace with"), mcp.Items(map[string]interface{}{"type": "number"})),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	ClearIssueLabelsTool = mcp.NewTool(
 | 
			
		||||
		ClearIssueLabelsToolName,
 | 
			
		||||
		mcp.WithDescription("Removes all labels from an issue"),
 | 
			
		||||
		mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
			
		||||
		mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
			
		||||
		mcp.WithNumber("index", mcp.Required(), mcp.Description("issue index")),
 | 
			
		||||
	)
 | 
			
		||||
 | 
			
		||||
	RemoveIssueLabelTool = mcp.NewTool(
 | 
			
		||||
		RemoveIssueLabelToolName,
 | 
			
		||||
		mcp.WithDescription("Removes a single label from an issue"),
 | 
			
		||||
		mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
			
		||||
		mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
 | 
			
		||||
		mcp.WithNumber("index", mcp.Required(), mcp.Description("issue index")),
 | 
			
		||||
		mcp.WithNumber("label_id", mcp.Required(), mcp.Description("label ID to remove")),
 | 
			
		||||
	)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	Tool.RegisterRead(server.ServerTool{
 | 
			
		||||
		Tool:    ListRepoLabelsTool,
 | 
			
		||||
		Handler: ListRepoLabelsFn,
 | 
			
		||||
	})
 | 
			
		||||
	Tool.RegisterRead(server.ServerTool{
 | 
			
		||||
		Tool:    GetRepoLabelTool,
 | 
			
		||||
		Handler: GetRepoLabelFn,
 | 
			
		||||
	})
 | 
			
		||||
	Tool.RegisterWrite(server.ServerTool{
 | 
			
		||||
		Tool:    CreateRepoLabelTool,
 | 
			
		||||
		Handler: CreateRepoLabelFn,
 | 
			
		||||
	})
 | 
			
		||||
	Tool.RegisterWrite(server.ServerTool{
 | 
			
		||||
		Tool:    EditRepoLabelTool,
 | 
			
		||||
		Handler: EditRepoLabelFn,
 | 
			
		||||
	})
 | 
			
		||||
	Tool.RegisterWrite(server.ServerTool{
 | 
			
		||||
		Tool:    DeleteRepoLabelTool,
 | 
			
		||||
		Handler: DeleteRepoLabelFn,
 | 
			
		||||
	})
 | 
			
		||||
	Tool.RegisterWrite(server.ServerTool{
 | 
			
		||||
		Tool:    AddIssueLabelsTool,
 | 
			
		||||
		Handler: AddIssueLabelsFn,
 | 
			
		||||
	})
 | 
			
		||||
	Tool.RegisterWrite(server.ServerTool{
 | 
			
		||||
		Tool:    ReplaceIssueLabelsTool,
 | 
			
		||||
		Handler: ReplaceIssueLabelsFn,
 | 
			
		||||
	})
 | 
			
		||||
	Tool.RegisterWrite(server.ServerTool{
 | 
			
		||||
		Tool:    ClearIssueLabelsTool,
 | 
			
		||||
		Handler: ClearIssueLabelsFn,
 | 
			
		||||
	})
 | 
			
		||||
	Tool.RegisterWrite(server.ServerTool{
 | 
			
		||||
		Tool:    RemoveIssueLabelTool,
 | 
			
		||||
		Handler: RemoveIssueLabelFn,
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ListRepoLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called ListRepoLabelsFn")
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
			
		||||
	}
 | 
			
		||||
	page, ok := req.GetArguments()["page"].(float64)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		page = 1
 | 
			
		||||
	}
 | 
			
		||||
	pageSize, ok := req.GetArguments()["pageSize"].(float64)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		pageSize = 100
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	opt := gitea_sdk.ListLabelsOptions{
 | 
			
		||||
		ListOptions: gitea_sdk.ListOptions{
 | 
			
		||||
			Page:     int(page),
 | 
			
		||||
			PageSize: int(pageSize),
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	client, err := gitea.ClientFromContext(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
	labels, _, err := client.ListRepoLabels(owner, repo, opt)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("list %v/%v/labels err: %v", owner, repo, err))
 | 
			
		||||
	}
 | 
			
		||||
	return to.TextResult(labels)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func GetRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called GetRepoLabelFn")
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
			
		||||
	}
 | 
			
		||||
	id, ok := req.GetArguments()["id"].(float64)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("label ID is required"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client, err := gitea.ClientFromContext(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
	label, _, err := client.GetRepoLabel(owner, repo, int64(id))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("get %v/%v/label/%v err: %v", owner, repo, int64(id), err))
 | 
			
		||||
	}
 | 
			
		||||
	return to.TextResult(label)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func CreateRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called CreateRepoLabelFn")
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
			
		||||
	}
 | 
			
		||||
	name, ok := req.GetArguments()["name"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("name is required"))
 | 
			
		||||
	}
 | 
			
		||||
	color, ok := req.GetArguments()["color"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("color is required"))
 | 
			
		||||
	}
 | 
			
		||||
	description, _ := req.GetArguments()["description"].(string) // Optional
 | 
			
		||||
 | 
			
		||||
	opt := gitea_sdk.CreateLabelOption{
 | 
			
		||||
		Name:        name,
 | 
			
		||||
		Color:       color,
 | 
			
		||||
		Description: description,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client, err := gitea.ClientFromContext(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
	label, _, err := client.CreateLabel(owner, repo, opt)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("create %v/%v/label err: %v", owner, repo, err))
 | 
			
		||||
	}
 | 
			
		||||
	return to.TextResult(label)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func EditRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called EditRepoLabelFn")
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
			
		||||
	}
 | 
			
		||||
	id, ok := req.GetArguments()["id"].(float64)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("label ID is required"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	opt := gitea_sdk.EditLabelOption{}
 | 
			
		||||
	if name, ok := req.GetArguments()["name"].(string); ok {
 | 
			
		||||
		opt.Name = ptr.To(name)
 | 
			
		||||
	}
 | 
			
		||||
	if color, ok := req.GetArguments()["color"].(string); ok {
 | 
			
		||||
		opt.Color = ptr.To(color)
 | 
			
		||||
	}
 | 
			
		||||
	if description, ok := req.GetArguments()["description"].(string); ok {
 | 
			
		||||
		opt.Description = ptr.To(description)
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client, err := gitea.ClientFromContext(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
	label, _, err := client.EditLabel(owner, repo, int64(id), opt)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("edit %v/%v/label/%v err: %v", owner, repo, int64(id), err))
 | 
			
		||||
	}
 | 
			
		||||
	return to.TextResult(label)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func DeleteRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called DeleteRepoLabelFn")
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
			
		||||
	}
 | 
			
		||||
	id, ok := req.GetArguments()["id"].(float64)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("label ID is required"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client, err := gitea.ClientFromContext(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
	_, err = client.DeleteLabel(owner, repo, int64(id))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("delete %v/%v/label/%v err: %v", owner, repo, int64(id), err))
 | 
			
		||||
	}
 | 
			
		||||
	return to.TextResult("Label deleted successfully")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func AddIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called AddIssueLabelsFn")
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
			
		||||
	}
 | 
			
		||||
	index, ok := req.GetArguments()["index"].(float64)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("issue index is required"))
 | 
			
		||||
	}
 | 
			
		||||
	labelsRaw, ok := req.GetArguments()["labels"].([]interface{})
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("labels (array of IDs) is required"))
 | 
			
		||||
	}
 | 
			
		||||
	var labels []int64
 | 
			
		||||
	for _, l := range labelsRaw {
 | 
			
		||||
		if labelID, ok := l.(float64); ok {
 | 
			
		||||
			labels = append(labels, int64(labelID))
 | 
			
		||||
		} else {
 | 
			
		||||
			return to.ErrorResult(fmt.Errorf("invalid label ID in labels array"))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	opt := gitea_sdk.IssueLabelsOption{
 | 
			
		||||
		Labels: labels,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client, err := gitea.ClientFromContext(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
	issueLabels, _, err := client.AddIssueLabels(owner, repo, int64(index), opt)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("add labels to %v/%v/issue/%v err: %v", owner, repo, int64(index), err))
 | 
			
		||||
	}
 | 
			
		||||
	return to.TextResult(issueLabels)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ReplaceIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called ReplaceIssueLabelsFn")
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
			
		||||
	}
 | 
			
		||||
	index, ok := req.GetArguments()["index"].(float64)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("issue index is required"))
 | 
			
		||||
	}
 | 
			
		||||
	labelsRaw, ok := req.GetArguments()["labels"].([]interface{})
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("labels (array of IDs) is required"))
 | 
			
		||||
	}
 | 
			
		||||
	var labels []int64
 | 
			
		||||
	for _, l := range labelsRaw {
 | 
			
		||||
		if labelID, ok := l.(float64); ok {
 | 
			
		||||
			labels = append(labels, int64(labelID))
 | 
			
		||||
		} else {
 | 
			
		||||
			return to.ErrorResult(fmt.Errorf("invalid label ID in labels array"))
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	opt := gitea_sdk.IssueLabelsOption{
 | 
			
		||||
		Labels: labels,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client, err := gitea.ClientFromContext(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
	issueLabels, _, err := client.ReplaceIssueLabels(owner, repo, int64(index), opt)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("replace labels on %v/%v/issue/%v err: %v", owner, repo, int64(index), err))
 | 
			
		||||
	}
 | 
			
		||||
	return to.TextResult(issueLabels)
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func ClearIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called ClearIssueLabelsFn")
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
			
		||||
	}
 | 
			
		||||
	index, ok := req.GetArguments()["index"].(float64)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("issue index is required"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client, err := gitea.ClientFromContext(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
	_, err = client.ClearIssueLabels(owner, repo, int64(index))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("clear labels on %v/%v/issue/%v err: %v", owner, repo, int64(index), err))
 | 
			
		||||
	}
 | 
			
		||||
	return to.TextResult("Labels cleared successfully")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func RemoveIssueLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called RemoveIssueLabelFn")
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("owner is required"))
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("repo is required"))
 | 
			
		||||
	}
 | 
			
		||||
	index, ok := req.GetArguments()["index"].(float64)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("issue index is required"))
 | 
			
		||||
	}
 | 
			
		||||
	labelID, ok := req.GetArguments()["label_id"].(float64)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("label ID is required"))
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	client, err := gitea.ClientFromContext(ctx)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
 | 
			
		||||
	}
 | 
			
		||||
	_, err = client.DeleteIssueLabel(owner, repo, int64(index), int64(labelID))
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return to.ErrorResult(fmt.Errorf("remove label %v from %v/%v/issue/%v err: %v", int64(labelID), owner, repo, int64(index), err))
 | 
			
		||||
	}
 | 
			
		||||
	return to.TextResult("Label removed successfully")
 | 
			
		||||
}
 | 
			
		||||
@@ -1,23 +1,27 @@
 | 
			
		||||
package operation
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"strings"
 | 
			
		||||
	"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"
 | 
			
		||||
	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
 | 
			
		||||
@@ -29,6 +33,9 @@ func RegisterTool(s *server.MCPServer) {
 | 
			
		||||
	// Issue Tool
 | 
			
		||||
	s.AddTools(issue.Tool.Tools()...)
 | 
			
		||||
 | 
			
		||||
	// Label Tool
 | 
			
		||||
	s.AddTools(label.Tool.Tools()...)
 | 
			
		||||
 | 
			
		||||
	// Pull Tool
 | 
			
		||||
	s.AddTools(pull.Tool.Tools()...)
 | 
			
		||||
 | 
			
		||||
@@ -41,22 +48,52 @@ func RegisterTool(s *server.MCPServer) {
 | 
			
		||||
	s.DeleteTools("")
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func getContextWithToken(ctx context.Context, r *http.Request) context.Context {
 | 
			
		||||
	authHeader := r.Header.Get("Authorization")
 | 
			
		||||
	if authHeader == "" {
 | 
			
		||||
		return ctx
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	parts := strings.Split(authHeader, " ")
 | 
			
		||||
	if len(parts) != 2 || parts[0] != "Bearer" {
 | 
			
		||||
		return ctx
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return context.WithValue(ctx, mcpContext.TokenContextKey, parts[1])
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
func Run() error {
 | 
			
		||||
	mcpServer = newMCPServer(flag.Version)
 | 
			
		||||
	RegisterTool(mcpServer)
 | 
			
		||||
	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)
 | 
			
		||||
		sseServer := server.NewSSEServer(
 | 
			
		||||
			mcpServer,
 | 
			
		||||
			server.WithSSEContextFunc(getContextWithToken),
 | 
			
		||||
		)
 | 
			
		||||
		log.Infof("Gitea MCP SSE server listening on :%d", flag.Port)
 | 
			
		||||
		if err := sseServer.Start(fmt.Sprintf(":%d", flag.Port)); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	case "http":
 | 
			
		||||
		httpServer := server.NewStreamableHTTPServer(
 | 
			
		||||
			mcpServer,
 | 
			
		||||
			server.WithLogger(log.New()),
 | 
			
		||||
			server.WithHeartbeatInterval(30*time.Second),
 | 
			
		||||
			server.WithHTTPContextFunc(getContextWithToken),
 | 
			
		||||
		)
 | 
			
		||||
		log.Infof("Gitea MCP HTTP server listening on :%d", flag.Port)
 | 
			
		||||
		if err := httpServer.Start(fmt.Sprintf(":%d", flag.Port)); err != nil {
 | 
			
		||||
			return err
 | 
			
		||||
		}
 | 
			
		||||
	default:
 | 
			
		||||
		return fmt.Errorf("invalid transport type: %s. Must be 'stdio' or 'sse'", flag.Mode)
 | 
			
		||||
		return fmt.Errorf("invalid transport type: %s. Must be 'stdio', 'sse' or 'http'", flag.Mode)
 | 
			
		||||
	}
 | 
			
		||||
	return nil
 | 
			
		||||
}
 | 
			
		||||
@@ -67,5 +104,6 @@ func newMCPServer(version string) *server.MCPServer {
 | 
			
		||||
		version,
 | 
			
		||||
		server.WithToolCapabilities(true),
 | 
			
		||||
		server.WithLogging(),
 | 
			
		||||
		server.WithRecovery(),
 | 
			
		||||
	)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -72,19 +72,23 @@ func init() {
 | 
			
		||||
 | 
			
		||||
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))
 | 
			
		||||
	}
 | 
			
		||||
@@ -94,25 +98,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
 | 
			
		||||
	}
 | 
			
		||||
@@ -125,7 +129,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))
 | 
			
		||||
	}
 | 
			
		||||
@@ -135,31 +143,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,
 | 
			
		||||
 
 | 
			
		||||
@@ -62,21 +62,25 @@ func init() {
 | 
			
		||||
 | 
			
		||||
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,
 | 
			
		||||
	})
 | 
			
		||||
@@ -89,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))
 | 
			
		||||
	}
 | 
			
		||||
@@ -111,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"))
 | 
			
		||||
	}
 | 
			
		||||
@@ -125,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))
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -17,8 +17,7 @@ const (
 | 
			
		||||
	ListRepoCommitsToolName = "list_repo_commits"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	ListRepoCommitsTool = mcp.NewTool(
 | 
			
		||||
var ListRepoCommitsTool = mcp.NewTool(
 | 
			
		||||
	ListRepoCommitsToolName,
 | 
			
		||||
	mcp.WithDescription("List repository commits"),
 | 
			
		||||
	mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
 | 
			
		||||
@@ -28,7 +27,6 @@ var (
 | 
			
		||||
	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{
 | 
			
		||||
@@ -39,24 +37,24 @@ func init() {
 | 
			
		||||
 | 
			
		||||
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),
 | 
			
		||||
@@ -65,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"
 | 
			
		||||
@@ -16,6 +19,7 @@ import (
 | 
			
		||||
 | 
			
		||||
const (
 | 
			
		||||
	GetFileToolName    = "get_file_content"
 | 
			
		||||
	GetDirToolName     = "get_dir_content"
 | 
			
		||||
	CreateFileToolName = "create_file"
 | 
			
		||||
	UpdateFileToolName = "update_file"
 | 
			
		||||
	DeleteFileToolName = "delete_file"
 | 
			
		||||
@@ -29,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(
 | 
			
		||||
@@ -50,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")),
 | 
			
		||||
	)
 | 
			
		||||
@@ -72,6 +86,10 @@ func init() {
 | 
			
		||||
		Tool:    GetFileContentTool,
 | 
			
		||||
		Handler: GetFileContentFn,
 | 
			
		||||
	})
 | 
			
		||||
	Tool.RegisterRead(server.ServerTool{
 | 
			
		||||
		Tool:    GetDirContentTool,
 | 
			
		||||
		Handler: GetDirContentFn,
 | 
			
		||||
	})
 | 
			
		||||
	Tool.RegisterWrite(server.ServerTool{
 | 
			
		||||
		Tool:    CreateFileTool,
 | 
			
		||||
		Handler: CreateFileFn,
 | 
			
		||||
@@ -86,45 +104,118 @@ func init() {
 | 
			
		||||
	})
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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{
 | 
			
		||||
@@ -133,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))
 | 
			
		||||
	}
 | 
			
		||||
@@ -142,25 +237,25 @@ 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,
 | 
			
		||||
@@ -170,7 +265,11 @@ func UpdateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
 | 
			
		||||
			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))
 | 
			
		||||
	}
 | 
			
		||||
@@ -179,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"))
 | 
			
		||||
	}
 | 
			
		||||
@@ -204,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))
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -34,6 +34,7 @@ var (
 | 
			
		||||
		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(
 | 
			
		||||
@@ -109,33 +110,39 @@ type ListReleaseResult struct {
 | 
			
		||||
 | 
			
		||||
func CreateReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called CreateReleasesFn")
 | 
			
		||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("owner is required")
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("repo is required")
 | 
			
		||||
	}
 | 
			
		||||
	tagName, ok := req.Params.Arguments["tag_name"].(string)
 | 
			
		||||
	tagName, ok := req.GetArguments()["tag_name"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("tag_name is required")
 | 
			
		||||
	}
 | 
			
		||||
	target, ok := req.Params.Arguments["target"].(string)
 | 
			
		||||
	target, ok := req.GetArguments()["target"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("target is required")
 | 
			
		||||
	}
 | 
			
		||||
	title, ok := req.Params.Arguments["title"].(string)
 | 
			
		||||
	title, ok := req.GetArguments()["title"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("title is required")
 | 
			
		||||
	}
 | 
			
		||||
	isDraft, _ := req.Params.Arguments["is_draft"].(bool)
 | 
			
		||||
	isPreRelease, _ := req.Params.Arguments["is_pre_release"].(bool)
 | 
			
		||||
	isDraft, _ := req.GetArguments()["is_draft"].(bool)
 | 
			
		||||
	isPreRelease, _ := req.GetArguments()["is_pre_release"].(bool)
 | 
			
		||||
	body, _ := req.GetArguments()["body"].(string)
 | 
			
		||||
 | 
			
		||||
	_, _, err := gitea.Client().CreateRelease(owner, repo, gitea_sdk.CreateReleaseOption{
 | 
			
		||||
	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,
 | 
			
		||||
	})
 | 
			
		||||
@@ -148,20 +155,24 @@ func CreateReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
 | 
			
		||||
 | 
			
		||||
func DeleteReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called DeleteReleaseFn")
 | 
			
		||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("owner is required")
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("repo is required")
 | 
			
		||||
	}
 | 
			
		||||
	id, ok := req.Params.Arguments["id"].(float64)
 | 
			
		||||
	id, ok := req.GetArguments()["id"].(float64)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("id is required")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := gitea.Client().DeleteRelease(owner, repo, int64(id))
 | 
			
		||||
	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)
 | 
			
		||||
	}
 | 
			
		||||
@@ -171,20 +182,24 @@ func DeleteReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
 | 
			
		||||
 | 
			
		||||
func GetReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called GetReleaseFn")
 | 
			
		||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("owner is required")
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("repo is required")
 | 
			
		||||
	}
 | 
			
		||||
	id, ok := req.Params.Arguments["id"].(float64)
 | 
			
		||||
	id, ok := req.GetArguments()["id"].(float64)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("id is required")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	release, _, err := gitea.Client().GetRelease(owner, repo, int64(id))
 | 
			
		||||
	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)
 | 
			
		||||
	}
 | 
			
		||||
@@ -194,16 +209,20 @@ func GetReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
 | 
			
		||||
 | 
			
		||||
func GetLatestReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called GetLatestReleaseFn")
 | 
			
		||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("owner is required")
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("repo is required")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	release, _, err := gitea.Client().GetLatestRelease(owner, repo)
 | 
			
		||||
	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)
 | 
			
		||||
	}
 | 
			
		||||
@@ -213,26 +232,38 @@ func GetLatestReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
 | 
			
		||||
 | 
			
		||||
func ListReleasesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called ListReleasesFn")
 | 
			
		||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("owner is required")
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("repo is required")
 | 
			
		||||
	}
 | 
			
		||||
	isDraft, _ := req.Params.Arguments["is_draft"].(bool)
 | 
			
		||||
	isPreRelease, _ := req.Params.Arguments["is_pre_release"].(bool)
 | 
			
		||||
	page, _ := req.Params.Arguments["page"].(float64)
 | 
			
		||||
	pageSize, _ := req.Params.Arguments["pageSize"].(float64)
 | 
			
		||||
	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)
 | 
			
		||||
 | 
			
		||||
	releases, _, err := gitea.Client().ListReleases(owner, repo, gitea_sdk.ListReleasesOptions{
 | 
			
		||||
	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:      ptr.To(isDraft),
 | 
			
		||||
		IsPreRelease: ptr.To(isPreRelease),
 | 
			
		||||
		IsDraft:      pIsDraft,
 | 
			
		||||
		IsPreRelease: pIsPreRelease,
 | 
			
		||||
	})
 | 
			
		||||
	if err != nil {
 | 
			
		||||
		return nil, fmt.Errorf("list releases error: %v", err)
 | 
			
		||||
 
 | 
			
		||||
@@ -27,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")),
 | 
			
		||||
@@ -38,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(
 | 
			
		||||
@@ -107,19 +108,20 @@ func RegisterTool(s *server.MCPServer) {
 | 
			
		||||
 | 
			
		||||
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,
 | 
			
		||||
@@ -133,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
 | 
			
		||||
@@ -164,7 +179,11 @@ 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("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))
 | 
			
		||||
	}
 | 
			
		||||
@@ -173,11 +192,11 @@ func ForkRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResu
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
	}
 | 
			
		||||
@@ -187,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))
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -87,22 +87,26 @@ type ListTagResult struct {
 | 
			
		||||
 | 
			
		||||
func CreateTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called CreateTagFn")
 | 
			
		||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("owner is required")
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("repo is required")
 | 
			
		||||
	}
 | 
			
		||||
	tagName, ok := req.Params.Arguments["tag_name"].(string)
 | 
			
		||||
	tagName, ok := req.GetArguments()["tag_name"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("tag_name is required")
 | 
			
		||||
	}
 | 
			
		||||
	target, _ := req.Params.Arguments["target"].(string)
 | 
			
		||||
	message, _ := req.Params.Arguments["message"].(string)
 | 
			
		||||
	target, _ := req.GetArguments()["target"].(string)
 | 
			
		||||
	message, _ := req.GetArguments()["message"].(string)
 | 
			
		||||
 | 
			
		||||
	_, _, err := gitea.Client().CreateTag(owner, repo, gitea_sdk.CreateTagOption{
 | 
			
		||||
	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,
 | 
			
		||||
@@ -116,20 +120,24 @@ func CreateTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRes
 | 
			
		||||
 | 
			
		||||
func DeleteTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called DeleteTagFn")
 | 
			
		||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("owner is required")
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("repo is required")
 | 
			
		||||
	}
 | 
			
		||||
	tagName, ok := req.Params.Arguments["tag_name"].(string)
 | 
			
		||||
	tagName, ok := req.GetArguments()["tag_name"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("tag_name is required")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	_, err := gitea.Client().DeleteTag(owner, repo, tagName)
 | 
			
		||||
	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)
 | 
			
		||||
	}
 | 
			
		||||
@@ -139,20 +147,24 @@ func DeleteTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRes
 | 
			
		||||
 | 
			
		||||
func GetTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called GetTagFn")
 | 
			
		||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("owner is required")
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("repo is required")
 | 
			
		||||
	}
 | 
			
		||||
	tagName, ok := req.Params.Arguments["tag_name"].(string)
 | 
			
		||||
	tagName, ok := req.GetArguments()["tag_name"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("tag_name is required")
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	tag, _, err := gitea.Client().GetTag(owner, repo, tagName)
 | 
			
		||||
	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)
 | 
			
		||||
	}
 | 
			
		||||
@@ -162,18 +174,22 @@ func GetTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult
 | 
			
		||||
 | 
			
		||||
func ListTagsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
 | 
			
		||||
	log.Debugf("Called ListTagsFn")
 | 
			
		||||
	owner, ok := req.Params.Arguments["owner"].(string)
 | 
			
		||||
	owner, ok := req.GetArguments()["owner"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("owner is required")
 | 
			
		||||
	}
 | 
			
		||||
	repo, ok := req.Params.Arguments["repo"].(string)
 | 
			
		||||
	repo, ok := req.GetArguments()["repo"].(string)
 | 
			
		||||
	if !ok {
 | 
			
		||||
		return nil, fmt.Errorf("repo is required")
 | 
			
		||||
	}
 | 
			
		||||
	page, _ := req.Params.Arguments["page"].(float64)
 | 
			
		||||
	pageSize, _ := req.Params.Arguments["pageSize"].(float64)
 | 
			
		||||
	page, _ := req.GetArguments()["page"].(float64)
 | 
			
		||||
	pageSize, _ := req.GetArguments()["pageSize"].(float64)
 | 
			
		||||
 | 
			
		||||
	tags, _, err := gitea.Client().ListRepoTags(owner, repo, gitea_sdk.ListRepoTagsOptions{
 | 
			
		||||
	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),
 | 
			
		||||
 
 | 
			
		||||
@@ -75,15 +75,15 @@ func init() {
 | 
			
		||||
 | 
			
		||||
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
 | 
			
		||||
	}
 | 
			
		||||
@@ -94,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))
 | 
			
		||||
	}
 | 
			
		||||
@@ -103,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
 | 
			
		||||
	}
 | 
			
		||||
@@ -128,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))
 | 
			
		||||
	}
 | 
			
		||||
@@ -137,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
 | 
			
		||||
	}
 | 
			
		||||
@@ -161,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{
 | 
			
		||||
@@ -170,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))
 | 
			
		||||
	}
 | 
			
		||||
 
 | 
			
		||||
@@ -15,68 +15,102 @@ import (
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
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(1)),
 | 
			
		||||
		mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
 | 
			
		||||
		mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(defaultPage)),
 | 
			
		||||
		mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(defaultPageSize)),
 | 
			
		||||
	)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
// init registers all MCP tools in Tool at package initialization.
 | 
			
		||||
// This function ensures the handler functions are registered before server usage.
 | 
			
		||||
func init() {
 | 
			
		||||
	Tool.RegisterRead(server.ServerTool{
 | 
			
		||||
		Tool:    GetMyUserInfoTool,
 | 
			
		||||
		Handler: GetUserInfoFn,
 | 
			
		||||
	})
 | 
			
		||||
 | 
			
		||||
	Tool.RegisterRead(server.ServerTool{
 | 
			
		||||
		Tool:    GetUserOrgsTool,
 | 
			
		||||
		Handler: GetUserOrgsFn,
 | 
			
		||||
	})
 | 
			
		||||
	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("Called GetUserOrgsFn")
 | 
			
		||||
	page, ok := req.Params.Arguments["page"].(float64)
 | 
			
		||||
	if !ok || page < 1 {
 | 
			
		||||
		page = 1
 | 
			
		||||
	}
 | 
			
		||||
	pageSize, ok := req.Params.Arguments["pageSize"].(float64)
 | 
			
		||||
	if !ok || pageSize < 1 {
 | 
			
		||||
		pageSize = 100
 | 
			
		||||
	}
 | 
			
		||||
	log.Debugf("[User] Called GetUserOrgsFn")
 | 
			
		||||
	page := getIntArg(req, "page", defaultPage)
 | 
			
		||||
	pageSize := getIntArg(req, "pageSize", defaultPageSize)
 | 
			
		||||
 | 
			
		||||
	opt := gitea_sdk.ListOrgsOptions{
 | 
			
		||||
		ListOptions: gitea_sdk.ListOptions{
 | 
			
		||||
			Page:     int(page),
 | 
			
		||||
			PageSize: int(pageSize),
 | 
			
		||||
			Page:     page,
 | 
			
		||||
			PageSize: pageSize,
 | 
			
		||||
		},
 | 
			
		||||
	}
 | 
			
		||||
	orgs, _, err := gitea.Client().ListMyOrgs(opt)
 | 
			
		||||
	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)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -19,12 +19,10 @@ const (
 | 
			
		||||
	GetGiteaMCPServerVersion = "get_gitea_mcp_server_version"
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
var (
 | 
			
		||||
	GetGiteaMCPServerVersionTool = mcp.NewTool(
 | 
			
		||||
var GetGiteaMCPServerVersionTool = mcp.NewTool(
 | 
			
		||||
	GetGiteaMCPServerVersion,
 | 
			
		||||
	mcp.WithDescription("Get Gitea MCP Server Version"),
 | 
			
		||||
)
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
func init() {
 | 
			
		||||
	Tool.RegisterRead(server.ServerTool{
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										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")
 | 
			
		||||
)
 | 
			
		||||
@@ -1,47 +1,47 @@
 | 
			
		||||
package gitea
 | 
			
		||||
 | 
			
		||||
import (
 | 
			
		||||
	"context"
 | 
			
		||||
	"crypto/tls"
 | 
			
		||||
	"fmt"
 | 
			
		||||
	"net/http"
 | 
			
		||||
	"sync"
 | 
			
		||||
 | 
			
		||||
	"gitea.com/gitea/gitea-mcp/pkg/flag"
 | 
			
		||||
	"gitea.com/gitea/gitea-mcp/pkg/log"
 | 
			
		||||
 | 
			
		||||
	"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 Client() *gitea.Client {
 | 
			
		||||
	clientOnce.Do(func() {
 | 
			
		||||
		var err error
 | 
			
		||||
		if client != nil {
 | 
			
		||||
			return
 | 
			
		||||
func NewClient(token string) (*gitea.Client, error) {
 | 
			
		||||
	httpClient := &http.Client{
 | 
			
		||||
		Transport: http.DefaultTransport,
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	opts := []gitea.ClientOption{
 | 
			
		||||
			gitea.SetToken(flag.Token),
 | 
			
		||||
		gitea.SetToken(token),
 | 
			
		||||
	}
 | 
			
		||||
	if flag.Insecure {
 | 
			
		||||
			httpClient := &http.Client{
 | 
			
		||||
				Transport: &http.Transport{
 | 
			
		||||
					TLSClientConfig: &tls.Config{
 | 
			
		||||
		httpClient.Transport.(*http.Transport).TLSClientConfig = &tls.Config{
 | 
			
		||||
			InsecureSkipVerify: true,
 | 
			
		||||
					},
 | 
			
		||||
				},
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
	opts = append(opts, gitea.SetHTTPClient(httpClient))
 | 
			
		||||
		}
 | 
			
		||||
	if flag.Debug {
 | 
			
		||||
		opts = append(opts, gitea.SetDebugMode())
 | 
			
		||||
	}
 | 
			
		||||
		client, err = gitea.NewClient(flag.Host, opts...)
 | 
			
		||||
	client, err := gitea.NewClient(flag.Host, opts...)
 | 
			
		||||
	if err != nil {
 | 
			
		||||
			log.Fatalf("create gitea client err: %v", err)
 | 
			
		||||
		return nil, fmt.Errorf("create gitea client err: %w", err)
 | 
			
		||||
	}
 | 
			
		||||
	})
 | 
			
		||||
	return client
 | 
			
		||||
 | 
			
		||||
	// 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)
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -19,7 +19,10 @@ var (
 | 
			
		||||
 | 
			
		||||
func Default() *zap.Logger {
 | 
			
		||||
	defaultLoggerOnce.Do(func() {
 | 
			
		||||
		if defaultLogger == nil {
 | 
			
		||||
		if defaultLogger != nil {
 | 
			
		||||
			return
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		ec := zap.NewProductionEncoderConfig()
 | 
			
		||||
		ec.EncodeTime = zapcore.TimeEncoderOfLayout(time.DateTime)
 | 
			
		||||
		ec.EncodeLevel = zapcore.CapitalLevelEncoder
 | 
			
		||||
@@ -32,14 +35,20 @@ func Default() *zap.Logger {
 | 
			
		||||
			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/gitea-mcp.log", home),
 | 
			
		||||
			Filename:   fmt.Sprintf("%s/gitea-mcp.log", logDir),
 | 
			
		||||
			MaxSize:    100,
 | 
			
		||||
			MaxBackups: 10,
 | 
			
		||||
			MaxAge:     30,
 | 
			
		||||
		}))
 | 
			
		||||
 | 
			
		||||
			if flag.Mode == "sse" {
 | 
			
		||||
		if flag.Mode == "http" || flag.Mode == "sse" {
 | 
			
		||||
			wss = append(wss, zapcore.AddSync(os.Stdout))
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
@@ -59,7 +68,6 @@ func Default() *zap.Logger {
 | 
			
		||||
			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) {
 | 
			
		||||
 
 | 
			
		||||
@@ -12,8 +12,8 @@ type Tool struct {
 | 
			
		||||
 | 
			
		||||
func New() *Tool {
 | 
			
		||||
	return &Tool{
 | 
			
		||||
		write: make([]server.ServerTool, 100),
 | 
			
		||||
		read:  make([]server.ServerTool, 100),
 | 
			
		||||
		write: make([]server.ServerTool, 0, 100),
 | 
			
		||||
		read:  make([]server.ServerTool, 0, 100),
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user