29 Commits

Author SHA1 Message Date
Bo-Yi Wu
8dc9ed9299 feat: add support for insecure mode in Gitea client configuration (#20)
- Add `GITEA_INTERACTIVE` configuration example in README files
- Add `insecure` flag to ignore TLS certificate errors in `cmd.go`
- Set insecure mode based on `GITEA_INSECURE` environment variable in `cmd.go`
- Add `Insecure` boolean variable in `pkg/flag/flag.go`
- Import `crypto/tls` and `net/http` in `pkg/gitea/gitea.go`
- Modify Gitea client creation to support insecure HTTP client in `pkg/gitea/gitea.go`

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/20
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-committed-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2025-04-08 01:16:37 +00:00
hiifong
1965c9830b fix bug 2025-04-06 14:59:06 +00:00
hiifong
f377f06478 fix typo 2025-04-06 14:47:37 +00:00
appleboy
02fd91da86 build: switch Docker images to Debian and optimize build process (#19)
- Switch base image from `golang:1.24-alpine` to `golang:1.24-bullseye` for the build stage
- Update working directory from `/build` to `/app`
- Separate the copying of go.mod and go.sum files before downloading dependencies
- Add comments for build stages and process steps
- Switch final stage base image from `ubuntu:24.04` to `debian:bullseye-slim`
- Improve installation of ca-certificates and clean up the apt lists afterward
- Create and switch to a non-root user named `gitea-mcp`
- Change the file copy command to `--chown=1000:1000 /app/gitea-mcp`
- Update `CMD` to use an absolute path `/app/gitea-mcp`

Signed-off-by: appleboy <appleboy.tw@gmail.com>

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/19
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2025-04-06 14:45:30 +00:00
appleboy
55f32ef4f5 docs: localize README with Chinese translations (#18)
- Add links to traditional and simplified Chinese versions of the README.
- Add README in Simplified Chinese with installation, usage, and troubleshooting instructions.
- Add README in Traditional Chinese with installation, usage, and troubleshooting instructions.

Signed-off-by: appleboy <appleboy.tw@gmail.com>

Co-authored-by: hiifong <i@hiif.ong>
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/18
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2025-04-06 14:07:17 +00:00
appleboy
c9cada1a8d docs: improve build system with enhanced targets and descriptions (#17)
- Add a `help` target to print a help message.
- Add descriptions for the `build`, `air`, `dev`, and `vendor` targets.
- Remove inline comments for the `air`, `dev`, and `vendor` targets.

Signed-off-by: appleboy <appleboy.tw@gmail.com>

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/17
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2025-04-06 14:06:53 +00:00
hiifong
a784029828 Update Dockerfile 2025-04-06 15:30:28 +08:00
hiifong
f27c4c622d Update Dockerfile 2025-04-06 15:28:05 +08:00
hiifong
df47a0c9eb Add Dockerfile 2025-04-06 13:09:14 +08:00
hiifong
e3307adbdf Add Dockerfile (#16)
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/16
Co-authored-by: hiifong <f@ilo.nz>
Co-committed-by: hiifong <f@ilo.nz>
2025-04-06 04:54:56 +00:00
hiifong
afada4435e Add Dockerfile (#15)
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/15
Co-authored-by: hiifong <f@ilo.nz>
Co-committed-by: hiifong <f@ilo.nz>
2025-04-06 04:46:27 +00:00
hiifong
6285bd2467 test (#14)
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/14
Co-authored-by: hiifong <f@ilo.nz>
Co-committed-by: hiifong <f@ilo.nz>
2025-04-06 04:12:06 +00:00
hiifong
5bbf8e0afb Add Dockerfile (#13)
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/13
Co-authored-by: hiifong <f@ilo.nz>
Co-committed-by: hiifong <f@ilo.nz>
2025-04-06 04:06:34 +00:00
appleboy
0535f5bab7 docs: improve documentation with new sections and better readability (#12)
- Add a "What is Gitea?" section explaining Gitea
- Add a "What is MCP?" section describing Model Context Protocol
- Reformat the MCP server configuration instructions for better readability
- Correct the markdown table for tool support
- Add a Troubleshooting section with common steps to resolve issues

Signed-off-by: appleboy <appleboy.tw@gmail.com>

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/12
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2025-04-05 05:06:34 +00:00
hiifong
d892b05048 Support custom sse port (#11)
fix: #10
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/11
2025-04-01 08:07:47 +00:00
hiifong
592cf51c9b fix bug (#9)
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/9
2025-03-28 03:10:29 +00:00
hiifong
2a9504fc5d update (#8)
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/8
2025-03-27 07:31:39 +00:00
hiifong
2f17f37053 fix bug (#7)
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/7
2025-03-27 07:18:51 +00:00
hiifong
5270d2eb08 Adding more logs (#6)
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/6
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: hiifong <i@hiif.ong>
Co-committed-by: hiifong <i@hiif.ong>
2025-03-25 17:22:39 +00:00
Lunny Xiao
97b98c3fc2 Merge pull request 'Add SHA to update file, convert get_file into get_file_content' (#5) from yp05327/gitea-mcp:add-sha-to-update-file into main
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/5
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-03-25 04:30:18 +00:00
hiifong
58328680a6 Merge branch 'main' into add-sha-to-update-file 2025-03-25 04:29:07 +00:00
Lunny Xiao
28947a030e Merge pull request 'Update README' (#4) from yp05327/gitea-mcp:update-readme into main
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/4
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
2025-03-25 04:28:34 +00:00
yp05327
d48e233fba update readme 2025-03-25 04:26:47 +00:00
yp05327
0e225f21da fix 2025-03-25 04:25:28 +00:00
yp05327
b9e575ad64 fix 2025-03-25 03:42:37 +00:00
yp05327
8395957553 update 2025-03-25 03:11:33 +00:00
yp05327
0aa33e0d62 change the emoji 2025-03-25 03:10:06 +00:00
yp05327
7d2a0985a3 change the emoji 2025-03-25 03:09:12 +00:00
yp05327
6f86512a7d update 2025-03-25 02:56:56 +00:00
23 changed files with 862 additions and 165 deletions

View File

@@ -0,0 +1,51 @@
name: release-nightly
on:
push:
branches: [main]
tags:
- "*"
jobs:
release-image:
runs-on: ubuntu-latest
env:
DOCKER_ORG: gitea
DOCKER_LATEST: nightly
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # all history for all branches and tags
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker BuildX
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Get Meta
id: meta
run: |
echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}') >> $GITHUB_OUTPUT
echo REPO_VERSION=$(git describe --tags --always | sed 's/-/+/' | sed 's/^v//') >> $GITHUB_OUTPUT
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
platforms: |
linux/amd64
linux/arm64
push: true
tags: |
${{ env.DOCKER_ORG }}/${{ steps.meta.outputs.REPO_NAME }}-server:${{ env.DOCKER_LATEST }}
build-args: |
VERSION=${{ steps.meta.outputs.REPO_VERSION }}

View File

@@ -36,3 +36,44 @@ jobs:
files: |-
bin/**
token: '${{secrets.RELEASE_TOKEN}}'
release-image:
runs-on: ubuntu-latest
env:
DOCKER_ORG: gitea
DOCKER_LATEST: latest
steps:
- name: Checkout
uses: actions/checkout@v4
with:
fetch-depth: 0 # all history for all branches and tags
- name: Set up QEMU
uses: docker/setup-qemu-action@v3
- name: Set up Docker BuildX
uses: docker/setup-buildx-action@v3
- name: Login to DockerHub
uses: docker/login-action@v3
with:
username: ${{ secrets.DOCKER_USER }}
password: ${{ secrets.DOCKER_TOKEN }}
- name: Get Meta
id: meta
run: |
echo REPO_NAME=$(echo ${GITHUB_REPOSITORY} | awk -F"/" '{print $2}') >> $GITHUB_OUTPUT
echo REPO_VERSION=${GITHUB_REF_NAME#v} >> $GITHUB_OUTPUT
- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
file: ./Dockerfile
platforms: |
linux/amd64
linux/arm64
push: true
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 }}

37
Dockerfile Normal file
View File

@@ -0,0 +1,37 @@
# Build stage
FROM golang:1.24-bullseye AS builder
ARG VERSION
# Set the working directory
WORKDIR /app
# Copy go.mod and go.sum files
COPY go.mod go.sum ./
# 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
# Final stage
FROM debian:bullseye-slim
WORKDIR /app
# Install ca-certificates for HTTPS requests
RUN apt-get update && \
apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
# 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
CMD ["/app/gitea-mcp", "-t", "stdio"]

View File

@@ -3,25 +3,30 @@ EXECUTABLE := gitea-mcp
VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')
LDFLAGS := -X "main.Version=$(VERSION)"
.PHONY: help
help: ## Print this help message.
@echo "Usage: make [target]"
@echo ""
@echo "Targets:"
@echo ""
@grep -E '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'
.PHONY: build
build:
build: ## Build the application.
$(GO) build -v -ldflags '-s -w $(LDFLAGS)' -o $(EXECUTABLE)
## air: install air for hot reload
.PHONY: air
air:
air: ## Install air for hot reload.
@hash air > /dev/null 2>&1; if [ $$? -ne 0 ]; then \
$(GO) install github.com/air-verse/air@latest; \
fi
## dev: run the application with hot reload
.PHONY: dev
dev: air
dev: air ## run the application with hot reload
air --build.cmd "make build" --build.bin ./gitea-mcp
## vendor: tidy and verify module dependencies
.PHONY: vendor
vendor:
vendor: ## tidy and verify module dependencies
@echo 'Tidying and verifying module dependencies...'
go mod tidy
go mod verify
go mod verify

135
README.md
View File

@@ -1,10 +1,65 @@
# Gitea MCP Server
[繁體中文](README.zh-tw.md) | [简体中文](README.zh-cn.md)
**Gitea MCP Server** is an integration plugin designed to connect Gitea with Model Context Protocol (MCP) systems. This allows for seamless command execution and repository management through an MCP-compatible chat interface.
[![Install with Docker in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](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}}) [![Install with Docker in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install_Server-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](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)
## 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.
## What is MCP?
Model Context Protocol (MCP) is a protocol that allows for the integration of various tools and systems through a chat interface. It enables seamless command execution and management of repositories, users, and other resources.
## 🚧 Installation
There is currently no official release. You will need to build the Gitea MCP Server from source.
### Usage with VS Code
For quick installation, use one of the one-click install buttons at the top of this README.
For manual installation, add the following JSON block to your User Settings (JSON) file in VS Code. You can do this by pressing `Ctrl + Shift + P` and typing `Preferences: Open User Settings (JSON)`.
Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace. This will allow you to share the configuration with others.
> Note that the `mcp` key is not needed in the `.vscode/mcp.json` file.
```json
{
"mcp": {
"inputs": [
{
"type": "promptString",
"id": "gitea_token",
"description": "Gitea Personal Access Token",
"password": true
}
],
"servers": {
"github": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"GITEA_ACCESS_TOKEN",
"docker.gitea.com/gitea-mcp-server"
],
"env": {
"GITEA_ACCESS_TOKEN": "${input:gitea_token}"
}
}
}
}
}
```
### 📥 Download the official binary release
You can download the official release from [here](https://gitea.com/gitea/gitea-mcp/releases).
### 🔧 Build from Source
@@ -25,7 +80,7 @@ Then run:
make build
```
### 🛠️ Add to PATH
### 📁 Add to PATH
After building, copy the binary gitea-mcp to a directory included in your system's PATH. For example:
@@ -39,18 +94,22 @@ This example is for Cursor, you can also use plugins in VSCode.
To configure the MCP server for Gitea, add the following to your MCP configuration file:
- **stdio mode**
```json
{
"mcpServers": {
"gitea": {
"command": "gitea-mcp",
"args": [
"-t", "stdio",
"--host", "https://gitea.com"
"-t",
"stdio",
"--host",
"https://gitea.com"
// "--token", "<your personal access token>"
],
"env": {
// "GITEA_HOST": "https://gitea.com",
// "GITEA_INTERACTIVE": "true",
"GITEA_ACCESS_TOKEN": "<your personal access token>"
}
}
@@ -59,6 +118,7 @@ To configure the MCP server for Gitea, add the following to your MCP configurati
```
- **sse mode**
```json
{
"mcpServers": {
@@ -69,6 +129,8 @@ To configure the MCP server for Gitea, add the following to your MCP configurati
}
```
**Default log path**: `$HOME/.gitea-mcp/gitea-mcp.log`
> [!NOTE]
> You can provide your Gitea host and access token either as command-line arguments or environment variables.
> Command-line arguments have the highest priority
@@ -79,42 +141,51 @@ Once everything is set up, try typing the following in your MCP-compatible chatb
list all my repositories
```
## ✅Available Tools
## ✅ Available Tools
The Gitea MCP Server supports the following tools:
| Tool | Scope | Description |
|:------:|:-------:|:------------:|
|get_my_user_info|User|Get the information of the authenticated user|
|create_repo|Repository|Create a new repository|
|fork_repo|Repository|Fork a repository|
|list_my_repos|Repository|List all repositories owned by the authenticated user|
|create_branch|Branch|Create a new branch|
|delete_branch|Branch|Delete a branch|
|list_branches|Branch|List all branches in a repository|
|list_repo_commits|Commit|List all commits in a repository|
|get_file|File|Get the content of a file|
|create_file|File|Create a new file|
|update_file|File|Update an existing file|
|delete_file|File|Delete a file|
|get_issue_by_index|Issue|Get an issue by its index|
|list_repo_issues|Issue|List all issues in a repository|
|create_issue|Issue|Create a new issue|
|create_issue_comment|Issue|Create a comment on an issue|
|get_pull_request_by_index|Pull Request|Get a pull request by its index|
|list_repo_pull_requests|Pull Request|List all pull requests in a repository|
|create_pull_request|Pull Request|Create a new pull request|
|search_users|User|Search for users|
|search_org_teams|Organization|Search for teams in an organization|
|search_repos|Repository|Search for repositories|
|get_gitea_mcp_server_version|Server|Get the version of the Gitea MCP Server|
| Tool | Scope | Description |
| :--------------------------: | :----------: | :---------------------------------------------------: |
| get_my_user_info | User | Get the information of the authenticated user |
| create_repo | Repository | Create a new repository |
| fork_repo | Repository | Fork a repository |
| list_my_repos | Repository | List all repositories owned by the authenticated user |
| create_branch | Branch | Create a new branch |
| delete_branch | Branch | Delete a branch |
| list_branches | Branch | List all branches in a repository |
| list_repo_commits | Commit | List all commits in a repository |
| get_file_content | File | Get the content and metadata of a file |
| create_file | File | Create a new file |
| update_file | File | Update an existing file |
| delete_file | File | Delete a file |
| get_issue_by_index | Issue | Get an issue by its index |
| list_repo_issues | Issue | List all issues in a repository |
| create_issue | Issue | Create a new issue |
| create_issue_comment | Issue | Create a comment on an issue |
| get_pull_request_by_index | Pull Request | Get a pull request by its index |
| list_repo_pull_requests | Pull Request | List all pull requests in a repository |
| create_pull_request | Pull Request | Create a new pull request |
| search_users | User | Search for users |
| search_org_teams | Organization | Search for teams in an organization |
| search_repos | Repository | Search for repositories |
| get_gitea_mcp_server_version | Server | Get the version of the Gitea MCP Server |
## 🐛 Debugging
To enable debug mode, add the `-d` flag when running the Gitea MCP Server with sse mode:
```sh
./gitea-mcp -t sse --token <your personal access token> -d
./gitea-mcp -t sse [--port 8080] --token <your personal access token> -d
```
## 🛠 Troubleshooting
If you encounter any issues, here are some common troubleshooting steps:
1. **Check your PATH**: Ensure that the `gitea-mcp` binary is in a directory included in your system's PATH.
2. **Verify dependencies**: Make sure you have all the required dependencies installed, such as `make` and `Golang`.
3. **Review configuration**: Double-check your MCP configuration file for any errors or missing information.
4. **Consult logs**: Check the logs for any error messages or warnings that can provide more information about the issue.
Enjoy exploring and managing your Gitea repositories via chat!

191
README.zh-cn.md Normal file
View File

@@ -0,0 +1,191 @@
# Gitea MCP 服务器
[English](README.md) | [繁體中文](README.zh-tw.md)
**Gitea MCP 服务器** 是一个集成插件,旨在将 Gitea 与 Model Context Protocol (MCP) 系统连接起来。这允许通过 MCP 兼容的聊天界面无缝执行命令和管理仓库。
[![在 VS Code 中使用 Docker 安装](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](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}}) [![在 VS Code Insiders 中使用 Docker 安装](https://img.shields.io/badge/VS_Code_Insiders-Install_Server-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](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
Gitea 是一个由社区管理的轻量级代码托管解决方案,使用 Go 语言编写。它以 MIT 许可证发布。Gitea 提供 Git 托管,包括仓库查看器、问题追踪、拉取请求等功能。
## 什么是 MCP
Model Context Protocol (MCP) 是一种协议,允许通过聊天界面整合各种工具和系统。它能够无缝执行命令和管理仓库、用户和其他资源。
## 🚧 安装
### 在 VS Code 中使用
要快速安装,请使用本 README 顶部的单击安装按钮之一。
要手动安装,请将以下 JSON 块添加到 VS Code 的用户设置 (JSON) 文件中。您可以通过按 `Ctrl + Shift + P` 并输入 `Preferences: Open User Settings (JSON)` 来完成此操作。
或者,您可以将其添加到工作区中的 `.vscode/mcp.json` 文件中。这将允许您与他人共享配置。
> 请注意,`.vscode/mcp.json` 文件中不需要 `mcp` 键。
```json
{
"mcp": {
"inputs": [
{
"type": "promptString",
"id": "gitea_token",
"description": "Gitea 个人访问令牌",
"password": true
}
],
"servers": {
"github": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"GITEA_ACCESS_TOKEN",
"docker.gitea.com/gitea-mcp-server"
],
"env": {
"GITEA_ACCESS_TOKEN": "${input:gitea_token}"
}
}
}
}
}
```
### 📥 下载官方二进制版本
您可以从[这里](https://gitea.com/gitea/gitea-mcp/releases)下载官方版本。
### 🔧 从源代码构建
您可以使用 Git 克隆仓库来下载源代码:
```bash
git clone https://gitea.com/gitea/gitea-mcp.git
```
在构建之前,请确保您已安装以下内容:
- make
- Golang (建议使用 Go 1.24 或更高版本)
然后运行:
```bash
make build
```
### 📁 添加到 PATH
构建后,将二进制文件 gitea-mcp 复制到系统 PATH 中包含的目录。例如:
```bash
cp gitea-mcp /usr/local/bin/
```
## 🚀 使用
此示例适用于 Cursor您也可以在 VSCode 中使用插件。
要配置 Gitea 的 MCP 服务器,请将以下内容添加到您的 MCP 配置文件中:
- **stdio 模式**
```json
{
"mcpServers": {
"gitea": {
"command": "gitea-mcp",
"args": [
"-t",
"stdio",
"--host",
"https://gitea.com"
// "--token", "<your personal access token>"
],
"env": {
// "GITEA_HOST": "https://gitea.com",
// "GITEA_INTERACTIVE": "true",
"GITEA_ACCESS_TOKEN": "<your personal access token>"
}
}
}
}
```
- **sse 模式**
```json
{
"mcpServers": {
"gitea": {
"url": "http://localhost:8080/sse"
}
}
}
```
**默认日志路径**: `$HOME/.gitea-mcp/gitea-mcp.log`
> [!注意]
> 您可以通过命令行参数或环境变量提供您的 Gitea 主机和访问令牌。
> 命令行参数具有最高优先级
一切设置完成后,请尝试在您的 MCP 兼容聊天框中输入以下内容:
```text
列出我所有的仓库
```
## ✅ 可用工具
Gitea MCP 服务器支持以下工具:
| 工具 | 范围 | 描述 |
| :--------------------------: | :------: | :--------------------------: |
| get_my_user_info | 用户 | 获取已认证用户的信息 |
| create_repo | 仓库 | 创建一个新仓库 |
| fork_repo | 仓库 | 复刻一个仓库 |
| list_my_repos | 仓库 | 列出已认证用户拥有的所有仓库 |
| create_branch | 分支 | 创建一个新分支 |
| delete_branch | 分支 | 删除一个分支 |
| list_branches | 分支 | 列出仓库中的所有分支 |
| list_repo_commits | 提交 | 列出仓库中的所有提交 |
| get_file_content | 文件 | 获取文件的内容和元数据 |
| create_file | 文件 | 创建一个新文件 |
| update_file | 文件 | 更新现有文件 |
| delete_file | 文件 | 删除一个文件 |
| get_issue_by_index | 问题 | 根据索引获取问题 |
| list_repo_issues | 问题 | 列出仓库中的所有问题 |
| create_issue | 问题 | 创建一个新问题 |
| create_issue_comment | 问题 | 在问题上创建评论 |
| get_pull_request_by_index | 拉取请求 | 根据索引获取拉取请求 |
| list_repo_pull_requests | 拉取请求 | 列出仓库中的所有拉取请求 |
| create_pull_request | 拉取请求 | 创建一个新拉取请求 |
| search_users | 用户 | 搜索用户 |
| search_org_teams | 组织 | 搜索组织中的团队 |
| search_repos | 仓库 | 搜索仓库 |
| get_gitea_mcp_server_version | 服务器 | 获取 Gitea MCP 服务器的版本 |
## 🐛 调试
要启用调试模式,请在使用 sse 模式运行 Gitea MCP 服务器时添加 `-d` 标志:
```sh
./gitea-mcp -t sse [--port 8080] --token <your personal access token> -d
```
## 🛠 疑难排解
如果您遇到任何问题,以下是一些常见的疑难排解步骤:
1. **检查您的 PATH**: 确保 `gitea-mcp` 二进制文件位于系统 PATH 中包含的目录中。
2. **验证依赖项**: 确保您已安装所有所需的依赖项,例如 `make``Golang`
3. **检查配置**: 仔细检查您的 MCP 配置文件是否有任何错误或遗漏的信息。
4. **查看日志**: 检查日志中是否有任何错误消息或警告,可以提供有关问题的更多信息。
享受通过聊天探索和管理您的 Gitea 仓库的乐趣!

191
README.zh-tw.md Normal file
View File

@@ -0,0 +1,191 @@
# Gitea MCP 伺服器
[English](README.md) | [简体中文](README.zh-cn.md)
**Gitea MCP 伺服器** 是一個整合插件,旨在將 Gitea 與 Model Context Protocol (MCP) 系統連接起來。這允許通過 MCP 兼容的聊天界面無縫執行命令和管理倉庫。
[![在 VS Code 中使用 Docker 安裝](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](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}}) [![在 VS Code Insiders 中使用 Docker 安裝](https://img.shields.io/badge/VS_Code_Insiders-Install_Server-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](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
Gitea 是一個由社群管理的輕量級代碼託管解決方案,使用 Go 語言編寫。它以 MIT 許可證發布。Gitea 提供 Git 託管,包括倉庫查看器、問題追蹤、拉取請求等功能。
## 什麼是 MCP
Model Context Protocol (MCP) 是一種協議,允許通過聊天界面整合各種工具和系統。它能夠無縫執行命令和管理倉庫、用戶和其他資源。
## 🚧 安裝
### 在 VS Code 中使用
要快速安裝,請使用本 README 頂部的單擊安裝按鈕之一。
要手動安裝,請將以下 JSON 塊添加到 VS Code 的用戶設置 (JSON) 文件中。您可以通過按 `Ctrl + Shift + P` 並輸入 `Preferences: Open User Settings (JSON)` 來完成此操作。
或者,您可以將其添加到工作區中的 `.vscode/mcp.json` 文件中。這將允許您與他人共享配置。
> 請注意,`.vscode/mcp.json` 文件中不需要 `mcp` 鍵。
```json
{
"mcp": {
"inputs": [
{
"type": "promptString",
"id": "gitea_token",
"description": "Gitea 個人訪問令牌",
"password": true
}
],
"servers": {
"github": {
"command": "docker",
"args": [
"run",
"-i",
"--rm",
"-e",
"GITEA_ACCESS_TOKEN",
"docker.gitea.com/gitea-mcp-server"
],
"env": {
"GITEA_ACCESS_TOKEN": "${input:gitea_token}"
}
}
}
}
}
```
### 📥 下載官方二進制版本
您可以從[這裡](https://gitea.com/gitea/gitea-mcp/releases)下載官方版本。
### 🔧 從源代碼構建
您可以使用 Git 克隆倉庫來下載源代碼:
```bash
git clone https://gitea.com/gitea/gitea-mcp.git
```
在構建之前,請確保您已安裝以下內容:
- make
- Golang (建議使用 Go 1.24 或更高版本)
然後運行:
```bash
make build
```
### 📁 添加到 PATH
構建後,將二進制文件 gitea-mcp 複製到系統 PATH 中包含的目錄。例如:
```bash
cp gitea-mcp /usr/local/bin/
```
## 🚀 使用
此示例適用於 Cursor您也可以在 VSCode 中使用插件。
要配置 Gitea 的 MCP 伺服器,請將以下內容添加到您的 MCP 配置文件中:
- **stdio 模式**
```json
{
"mcpServers": {
"gitea": {
"command": "gitea-mcp",
"args": [
"-t",
"stdio",
"--host",
"https://gitea.com"
// "--token", "<your personal access token>"
],
"env": {
// "GITEA_HOST": "https://gitea.com",
// "GITEA_INTERACTIVE": "true",
"GITEA_ACCESS_TOKEN": "<your personal access token>"
}
}
}
}
```
- **sse 模式**
```json
{
"mcpServers": {
"gitea": {
"url": "http://localhost:8080/sse"
}
}
}
```
**默認日誌路徑**: `$HOME/.gitea-mcp/gitea-mcp.log`
> [!注意]
> 您可以通過命令行參數或環境變量提供您的 Gitea 主機和訪問令牌。
> 命令行參數具有最高優先級
一切設置完成後,請嘗試在您的 MCP 兼容聊天框中輸入以下內容:
```text
列出我所有的倉庫
```
## ✅ 可用工具
Gitea MCP 伺服器支持以下工具:
| 工具 | 範圍 | 描述 |
| :--------------------------: | :------: | :--------------------------: |
| get_my_user_info | 用戶 | 獲取已認證用戶的信息 |
| create_repo | 倉庫 | 創建一個新倉庫 |
| fork_repo | 倉庫 | 復刻一個倉庫 |
| list_my_repos | 倉庫 | 列出已認證用戶擁有的所有倉庫 |
| create_branch | 分支 | 創建一個新分支 |
| delete_branch | 分支 | 刪除一個分支 |
| list_branches | 分支 | 列出倉庫中的所有分支 |
| list_repo_commits | 提交 | 列出倉庫中的所有提交 |
| get_file_content | 文件 | 獲取文件的內容和元數據 |
| create_file | 文件 | 創建一個新文件 |
| update_file | 文件 | 更新現有文件 |
| delete_file | 文件 | 刪除一個文件 |
| get_issue_by_index | 問題 | 根據索引獲取問題 |
| list_repo_issues | 問題 | 列出倉庫中的所有問題 |
| create_issue | 問題 | 創建一個新問題 |
| create_issue_comment | 問題 | 在問題上創建評論 |
| get_pull_request_by_index | 拉取請求 | 根據索引獲取拉取請求 |
| list_repo_pull_requests | 拉取請求 | 列出倉庫中的所有拉取請求 |
| create_pull_request | 拉取請求 | 創建一個新拉取請求 |
| search_users | 用戶 | 搜索用戶 |
| search_org_teams | 組織 | 搜索組織中的團隊 |
| search_repos | 倉庫 | 搜索倉庫 |
| get_gitea_mcp_server_version | 伺服器 | 獲取 Gitea MCP 伺服器的版本 |
## 🐛 調試
要啟用調試模式,請在使用 sse 模式運行 Gitea MCP 伺服器時添加 `-d` 標誌:
```sh
./gitea-mcp -t sse [--port 8080] --token <your personal access token> -d
```
## 🛠 疑難排解
如果您遇到任何問題,以下是一些常見的疑難排解步驟:
1. **檢查您的 PATH**: 確保 `gitea-mcp` 二進制文件位於系統 PATH 中包含的目錄中。
2. **驗證依賴項**: 確保您已安裝所有所需的依賴項,例如 `make``Golang`
3. **檢查配置**: 仔細檢查您的 MCP 配置文件是否有任何錯誤或遺漏的信息。
4. **查看日誌**: 檢查日誌中是否有任何錯誤消息或警告,可以提供有關問題的更多信息。
享受通過聊天探索和管理您的 Gitea 倉庫的樂趣!

View File

@@ -13,6 +13,7 @@ import (
var (
transport string
host string
port int
token string
debug bool
@@ -37,6 +38,12 @@ func init() {
"https://gitea.com",
"Gitea host",
)
flag.IntVar(
&port,
"port",
8080,
"sse port",
)
flag.StringVar(
&token,
"token",
@@ -55,6 +62,12 @@ func init() {
true,
"debug mode",
)
flag.BoolVar(
&flagPkg.Insecure,
"insecure",
false,
"ignore TLS certificate errors",
)
flag.Parse()
@@ -66,17 +79,26 @@ func init() {
flagPkg.Host = "https://gitea.com"
}
flagPkg.Port = port
flagPkg.Token = token
if flagPkg.Token == "" {
flagPkg.Token = os.Getenv("GITEA_ACCESS_TOKEN")
}
flagPkg.Mode = transport
if debug {
flagPkg.Debug = debug
}
if !debug {
flagPkg.Debug = os.Getenv("GITEA_DEBUG") == "true"
}
// Set insecure mode based on environment variable
if os.Getenv("GITEA_INSECURE") == "true" {
flagPkg.Insecure = true
}
}
func Execute(version string) {

7
go.mod
View File

@@ -3,9 +3,10 @@ module gitea.com/gitea/gitea-mcp
go 1.24.0
require (
code.gitea.io/sdk/gitea v0.20.0
github.com/mark3labs/mcp-go v0.15.0
code.gitea.io/sdk/gitea v0.21.0
github.com/mark3labs/mcp-go v0.18.0
go.uber.org/zap v1.27.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1
)
require (
@@ -17,5 +18,5 @@ require (
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.36.0 // indirect
golang.org/x/sys v0.31.0 // indirect
golang.org/x/sys v0.32.0 // indirect
)

18
go.sum
View File

@@ -1,5 +1,5 @@
code.gitea.io/sdk/gitea v0.20.0 h1:Zm/QDwwZK1awoM4AxdjeAQbxolzx2rIP8dDfmKu+KoU=
code.gitea.io/sdk/gitea v0.20.0/go.mod h1:faouBHC/zyx5wLgjmRKR62ydyvMzwWf3QnU0bH7Cw6U=
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/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
@@ -12,12 +12,12 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY=
github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/mark3labs/mcp-go v0.15.0 h1:lViiC4dk6chJHZccezaTzZLMOQVUXJDGNQPtzExr5NQ=
github.com/mark3labs/mcp-go v0.15.0/go.mod h1:xBB350hekQsJAK7gJAii8bcEoWemboLm2mRm5/+KBaU=
github.com/mark3labs/mcp-go v0.18.0 h1:YuhgIVjNlTG2ZOwmrkORWyPTp0dz1opPEqvsPtySXao=
github.com/mark3labs/mcp-go v0.18.0/go.mod h1:KmJndYv7GIgcPVwEKJjNcbhVQ+hJGJhrCCB/9xITzpE=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
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/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=
@@ -36,13 +36,15 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v
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.31.0 h1:ioabZlmFYtWhL+TRYpcnNlLwhyxaM9kWTDEmfnprqik=
golang.org/x/sys v0.31.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.30.0 h1:PQ39fJZ+mfadBm0y5WlL4vlM7Sx1Hgf13sMIY2+QS9Y=
golang.org/x/term v0.30.0/go.mod h1:NYYFdzHoI5wRh/h5tDMdMqCqPJZEuNqVR5xJLd/n67g=
golang.org/x/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/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=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

View File

@@ -68,19 +68,19 @@ func GetIssueByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
log.Debugf("Called GetIssueByIndexFn")
owner, ok := req.Params.Arguments["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
return to.ErrorResult(fmt.Errorf("owner is required"))
}
repo, ok := req.Params.Arguments["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
return to.ErrorResult(fmt.Errorf("repo is required"))
}
index, ok := req.Params.Arguments["index"].(float64)
if !ok {
return nil, fmt.Errorf("index is required")
return to.ErrorResult(fmt.Errorf("index is required"))
}
issue, _, err := gitea.Client().GetIssue(owner, repo, int64(index))
if err != nil {
return nil, fmt.Errorf("get %v/%v/issue/%v err", owner, repo, int64(index))
return to.ErrorResult(fmt.Errorf("get %v/%v/issue/%v err: %v", owner, repo, int64(index), err))
}
return to.TextResult(issue)
@@ -90,11 +90,11 @@ func ListRepoIssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
log.Debugf("Called ListIssuesFn")
owner, ok := req.Params.Arguments["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
return to.ErrorResult(fmt.Errorf("owner is required"))
}
repo, ok := req.Params.Arguments["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
return to.ErrorResult(fmt.Errorf("repo is required"))
}
state, ok := req.Params.Arguments["state"].(string)
if !ok {
@@ -117,7 +117,7 @@ func ListRepoIssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
}
issues, _, err := gitea.Client().ListRepoIssues(owner, repo, opt)
if err != nil {
return nil, fmt.Errorf("get %v/%v/issues err", owner, repo)
return to.ErrorResult(fmt.Errorf("get %v/%v/issues err: %v", owner, repo, err))
}
return to.TextResult(issues)
}
@@ -126,26 +126,26 @@ func CreateIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
log.Debugf("Called CreateIssueFn")
owner, ok := req.Params.Arguments["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
return to.ErrorResult(fmt.Errorf("owner is required"))
}
repo, ok := req.Params.Arguments["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
return to.ErrorResult(fmt.Errorf("repo is required"))
}
title, ok := req.Params.Arguments["title"].(string)
if !ok {
return nil, fmt.Errorf("title is required")
return to.ErrorResult(fmt.Errorf("title is required"))
}
body, ok := req.Params.Arguments["body"].(string)
if !ok {
return nil, fmt.Errorf("body is required")
return to.ErrorResult(fmt.Errorf("body is required"))
}
issue, _, err := gitea.Client().CreateIssue(owner, repo, gitea_sdk.CreateIssueOption{
Title: title,
Body: body,
})
if err != nil {
return nil, fmt.Errorf("create %v/%v/issue err", owner, repo)
return to.ErrorResult(fmt.Errorf("create %v/%v/issue err", owner, repo))
}
return to.TextResult(issue)
@@ -155,26 +155,26 @@ func CreateIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca
log.Debugf("Called CreateIssueCommentFn")
owner, ok := req.Params.Arguments["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
return to.ErrorResult(fmt.Errorf("owner is required"))
}
repo, ok := req.Params.Arguments["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
return to.ErrorResult(fmt.Errorf("repo is required"))
}
index, ok := req.Params.Arguments["index"].(float64)
if !ok {
return nil, fmt.Errorf("index is required")
return to.ErrorResult(fmt.Errorf("index is required"))
}
body, ok := req.Params.Arguments["body"].(string)
if !ok {
return nil, fmt.Errorf("body is required")
return to.ErrorResult(fmt.Errorf("body is required"))
}
opt := gitea_sdk.CreateIssueCommentOption{
Body: body,
}
issueComment, _, err := gitea.Client().CreateIssueComment(owner, repo, int64(index), opt)
if err != nil {
return nil, fmt.Errorf("create %v/%v/issue/%v/comment err", owner, repo, int64(index))
return to.ErrorResult(fmt.Errorf("create %v/%v/issue/%v/comment err", owner, repo, int64(index)))
}
return to.TextResult(issueComment)

View File

@@ -50,8 +50,8 @@ func Run(transport, version string) error {
}
case "sse":
sseServer := server.NewSSEServer(mcpServer)
log.Infof("Gitea MCP SSE server listening on :8080")
if err := sseServer.Start(":8080"); err != nil {
log.Infof("Gitea MCP SSE server listening on :%d", flag.Port)
if err := sseServer.Start(fmt.Sprintf(":%d", flag.Port)); err != nil {
return err
}
default:

View File

@@ -33,9 +33,11 @@ var (
mcp.WithDescription("List repository pull requests"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("state", mcp.Description("state")),
mcp.WithString("sort", mcp.Description("sort")),
mcp.WithString("state", mcp.Description("state"), mcp.Enum("open", "closed", "all"), mcp.DefaultString("all")),
mcp.WithString("sort", mcp.Description("sort"), mcp.Enum("oldest", "recentupdate", "leastupdate", "mostcomment", "leastcomment", "priority"), mcp.DefaultString("recentupdate")),
mcp.WithNumber("milestone", mcp.Description("milestone")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
CreatePullRequestTool = mcp.NewTool(
@@ -60,19 +62,19 @@ func GetPullRequestByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp
log.Debugf("Called GetPullRequestByIndexFn")
owner, ok := req.Params.Arguments["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
return to.ErrorResult(fmt.Errorf("owner is required"))
}
repo, ok := req.Params.Arguments["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
return to.ErrorResult(fmt.Errorf("repo is required"))
}
index, ok := req.Params.Arguments["index"].(float64)
if !ok {
return nil, fmt.Errorf("index is required")
return to.ErrorResult(fmt.Errorf("index is required"))
}
pr, _, err := gitea.Client().GetPullRequest(owner, repo, int64(index))
if err != nil {
return nil, fmt.Errorf("get %v/%v/pr/%v err", owner, repo, int64(index))
return to.ErrorResult(fmt.Errorf("get %v/%v/pr/%v err: %v", owner, repo, int64(index), err))
}
return to.TextResult(pr)
@@ -82,27 +84,38 @@ func ListRepoPullRequestsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.
log.Debugf("Called ListRepoPullRequests")
owner, ok := req.Params.Arguments["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
return to.ErrorResult(fmt.Errorf("owner is required"))
}
repo, ok := req.Params.Arguments["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
return to.ErrorResult(fmt.Errorf("repo is required"))
}
state, _ := req.Params.Arguments["state"].(string)
sort, _ := req.Params.Arguments["sort"].(string)
sort, ok := req.Params.Arguments["sort"].(string)
if !ok {
sort = "recentupdate"
}
milestone, _ := req.Params.Arguments["milestone"].(float64)
page, ok := req.Params.Arguments["page"].(float64)
if !ok {
page = 1
}
pageSize, ok := req.Params.Arguments["pageSize"].(float64)
if !ok {
pageSize = 100
}
opt := gitea_sdk.ListPullRequestsOptions{
State: gitea_sdk.StateType(state),
Sort: sort,
Milestone: int64(milestone),
ListOptions: gitea_sdk.ListOptions{
Page: 1,
PageSize: 1000,
Page: int(page),
PageSize: int(pageSize),
},
}
pullRequests, _, err := gitea.Client().ListRepoPullRequests("", "", opt)
pullRequests, _, err := gitea.Client().ListRepoPullRequests(owner, repo, opt)
if err != nil {
return nil, fmt.Errorf("list %v/%v/pull_requests err", owner, repo)
return to.ErrorResult(fmt.Errorf("list %v/%v/pull_requests err: %v", owner, repo, err))
}
return to.TextResult(pullRequests)
@@ -112,27 +125,27 @@ func CreatePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Cal
log.Debugf("Called CreatePullRequestFn")
owner, ok := req.Params.Arguments["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
return to.ErrorResult(fmt.Errorf("owner is required"))
}
repo, ok := req.Params.Arguments["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
return to.ErrorResult(fmt.Errorf("repo is required"))
}
title, ok := req.Params.Arguments["title"].(string)
if !ok {
return nil, fmt.Errorf("title is required")
return to.ErrorResult(fmt.Errorf("title is required"))
}
body, ok := req.Params.Arguments["body"].(string)
if !ok {
return nil, fmt.Errorf("body is required")
return to.ErrorResult(fmt.Errorf("body is required"))
}
head, ok := req.Params.Arguments["head"].(string)
if !ok {
return nil, fmt.Errorf("head is required")
return to.ErrorResult(fmt.Errorf("head is required"))
}
base, ok := req.Params.Arguments["base"].(string)
if !ok {
return nil, fmt.Errorf("base is required")
return to.ErrorResult(fmt.Errorf("base is required"))
}
pr, _, err := gitea.Client().CreatePullRequest(owner, repo, gitea_sdk.CreatePullRequestOption{
Title: title,
@@ -141,7 +154,7 @@ func CreatePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Cal
Base: base,
})
if err != nil {
return nil, fmt.Errorf("create %v/%v/pull_request err", owner, repo)
return to.ErrorResult(fmt.Errorf("create %v/%v/pull_request err: %v", owner, repo, err))
}
return to.TextResult(pr)

View File

@@ -48,15 +48,15 @@ func CreateBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
log.Debugf("Called CreateBranchFn")
owner, ok := req.Params.Arguments["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
return to.ErrorResult(fmt.Errorf("owner is required"))
}
repo, ok := req.Params.Arguments["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
return to.ErrorResult(fmt.Errorf("repo is required"))
}
branch, ok := req.Params.Arguments["branch"].(string)
if !ok {
return nil, fmt.Errorf("branch is required")
return to.ErrorResult(fmt.Errorf("branch is required"))
}
oldBranch, _ := req.Params.Arguments["old_branch"].(string)
@@ -65,7 +65,7 @@ func CreateBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
OldBranchName: oldBranch,
})
if err != nil {
return nil, fmt.Errorf("Create Branch Error: %v", err)
return to.ErrorResult(fmt.Errorf("create branch error: %v", err))
}
return mcp.NewToolResultText("Branch Created"), nil
@@ -75,19 +75,19 @@ func DeleteBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
log.Debugf("Called DeleteBranchFn")
owner, ok := req.Params.Arguments["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
return to.ErrorResult(fmt.Errorf("owner is required"))
}
repo, ok := req.Params.Arguments["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
return to.ErrorResult(fmt.Errorf("repo is required"))
}
branch, ok := req.Params.Arguments["branch"].(string)
if !ok {
return nil, fmt.Errorf("branch is required")
return to.ErrorResult(fmt.Errorf("branch is required"))
}
_, _, err := gitea.Client().DeleteRepoBranch(owner, repo, branch)
if err != nil {
return nil, fmt.Errorf("Delete Branch Error: %v", err)
return to.ErrorResult(fmt.Errorf("delete branch error: %v", err))
}
return to.TextResult("Branch Deleted")
@@ -97,11 +97,11 @@ func ListBranchesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
log.Debugf("Called ListBranchesFn")
owner, ok := req.Params.Arguments["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
return to.ErrorResult(fmt.Errorf("owner is required"))
}
repo, ok := req.Params.Arguments["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
return to.ErrorResult(fmt.Errorf("repo is required"))
}
opt := gitea_sdk.ListRepoBranchesOptions{
ListOptions: gitea_sdk.ListOptions{
@@ -111,7 +111,7 @@ func ListBranchesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
}
branches, _, err := gitea.Client().ListRepoBranches(owner, repo, opt)
if err != nil {
return nil, fmt.Errorf("List Branches Error: %v", err)
return to.ErrorResult(fmt.Errorf("list branches error: %v", err))
}
return to.TextResult(branches)

View File

@@ -22,8 +22,10 @@ var (
mcp.WithDescription("List repository commits"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("sha", mcp.Description("sha")),
mcp.WithString("path", mcp.Description("path")),
mcp.WithString("sha", mcp.Description("SHA or branch to start listing commits from")),
mcp.WithString("path", mcp.Description("path indicates that only commits that include the path's file/dir should be returned.")),
mcp.WithNumber("page", mcp.Required(), mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)),
mcp.WithNumber("page_size", mcp.Required(), mcp.Description("page size"), mcp.DefaultNumber(50), mcp.Min(1)),
)
)
@@ -31,25 +33,33 @@ func ListRepoCommitsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
log.Debugf("Called ListRepoCommitsFn")
owner, ok := req.Params.Arguments["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
return to.ErrorResult(fmt.Errorf("owner is required"))
}
repo, ok := req.Params.Arguments["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
return to.ErrorResult(fmt.Errorf("repo is required"))
}
page, ok := req.Params.Arguments["page"].(float64)
if !ok {
return to.ErrorResult(fmt.Errorf("page is required"))
}
pageSize, ok := req.Params.Arguments["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)
opt := gitea_sdk.ListCommitOptions{
ListOptions: gitea_sdk.ListOptions{
Page: 1,
PageSize: 1000,
Page: int(page),
PageSize: int(pageSize),
},
SHA: sha,
Path: path,
}
commits, _, err := gitea.Client().ListRepoCommits(owner, repo, opt)
if err != nil {
return nil, fmt.Errorf("list repo commits err: %v", err)
return to.ErrorResult(fmt.Errorf("list repo commits err: %v", err))
}
return to.TextResult(commits)
}

View File

@@ -2,6 +2,7 @@ package repo
import (
"context"
"encoding/base64"
"fmt"
"gitea.com/gitea/gitea-mcp/pkg/gitea"
@@ -13,19 +14,19 @@ import (
)
const (
GetFileToolName = "get_file"
GetFileToolName = "get_file_content"
CreateFileToolName = "create_file"
UpdateFileToolName = "update_file"
DeleteFileToolName = "delete_file"
)
var (
GetFileTool = mcp.NewTool(
GetFileContentTool = mcp.NewTool(
GetFileToolName,
mcp.WithDescription("Get file"),
mcp.WithDescription("Get file Content and Metadata"),
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")),
mcp.WithString("ref", mcp.Required(), mcp.Description("ref can be branch/tag/commit")),
mcp.WithString("filePath", mcp.Required(), mcp.Description("file path")),
)
@@ -47,7 +48,8 @@ var (
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("filePath", mcp.Required(), mcp.Description("file path")),
mcp.WithString("content", mcp.Required(), mcp.Description("file content")),
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("message", mcp.Required(), mcp.Description("commit message")),
mcp.WithString("branch_name", mcp.Required(), mcp.Description("branch name")),
)
@@ -64,47 +66,47 @@ var (
)
)
func GetFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
func GetFileContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetFileFn")
owner, ok := req.Params.Arguments["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
return to.ErrorResult(fmt.Errorf("owner is required"))
}
repo, ok := req.Params.Arguments["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
return to.ErrorResult(fmt.Errorf("repo is required"))
}
ref, _ := req.Params.Arguments["ref"].(string)
filePath, ok := req.Params.Arguments["filePath"].(string)
if !ok {
return nil, fmt.Errorf("filePath is required")
return to.ErrorResult(fmt.Errorf("filePath is required"))
}
file, _, err := gitea.Client().GetFile(owner, repo, ref, filePath)
content, _, err := gitea.Client().GetContents(owner, repo, ref, filePath)
if err != nil {
return nil, fmt.Errorf("get file err: %v", err)
return to.ErrorResult(fmt.Errorf("get file err: %v", err))
}
return to.TextResult(file)
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)
if !ok {
return nil, fmt.Errorf("owner is required")
return to.ErrorResult(fmt.Errorf("owner is required"))
}
repo, ok := req.Params.Arguments["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
return to.ErrorResult(fmt.Errorf("repo is required"))
}
filePath, ok := req.Params.Arguments["filePath"].(string)
if !ok {
return nil, fmt.Errorf("filePath is required")
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)
opt := gitea_sdk.CreateFileOptions{
Content: content,
Content: base64.StdEncoding.EncodeToString([]byte(content)),
FileOptions: gitea_sdk.FileOptions{
Message: message,
BranchName: branchName,
@@ -113,7 +115,7 @@ func CreateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
_, _, err := gitea.Client().CreateFile(owner, repo, filePath, opt)
if err != nil {
return nil, fmt.Errorf("create file err: %v", err)
return to.ErrorResult(fmt.Errorf("create file err: %v", err))
}
return to.TextResult("Create file success")
}
@@ -122,20 +124,26 @@ func UpdateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
log.Debugf("Called UpdateFileFn")
owner, ok := req.Params.Arguments["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
return to.ErrorResult(fmt.Errorf("owner is required"))
}
repo, ok := req.Params.Arguments["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
return to.ErrorResult(fmt.Errorf("repo is required"))
}
filePath, ok := req.Params.Arguments["filePath"].(string)
if !ok {
return nil, fmt.Errorf("filePath is required")
return to.ErrorResult(fmt.Errorf("filePath is required"))
}
sha, ok := req.Params.Arguments["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)
opt := gitea_sdk.UpdateFileOptions{
SHA: sha,
Content: content,
FileOptions: gitea_sdk.FileOptions{
Message: message,
@@ -144,7 +152,7 @@ func UpdateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
}
_, _, err := gitea.Client().UpdateFile(owner, repo, filePath, opt)
if err != nil {
return nil, fmt.Errorf("update file err: %v", err)
return to.ErrorResult(fmt.Errorf("update file err: %v", err))
}
return to.TextResult("Update file success")
}
@@ -153,27 +161,32 @@ func DeleteFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
log.Debugf("Called DeleteFileFn")
owner, ok := req.Params.Arguments["owner"].(string)
if !ok {
return nil, fmt.Errorf("owner is required")
return to.ErrorResult(fmt.Errorf("owner is required"))
}
repo, ok := req.Params.Arguments["repo"].(string)
if !ok {
return nil, fmt.Errorf("repo is required")
return to.ErrorResult(fmt.Errorf("repo is required"))
}
filePath, ok := req.Params.Arguments["filePath"].(string)
if !ok {
return nil, fmt.Errorf("filePath is required")
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)
if !ok {
return to.ErrorResult(fmt.Errorf("sha is required"))
}
opt := gitea_sdk.DeleteFileOptions{
FileOptions: gitea_sdk.FileOptions{
Message: message,
BranchName: branchName,
},
SHA: sha,
}
_, err := gitea.Client().DeleteFile(owner, repo, filePath, opt)
if err != nil {
return nil, fmt.Errorf("delete file err: %v", err)
return to.ErrorResult(fmt.Errorf("delete file err: %v", err))
}
return to.TextResult("Delete file success")
}

View File

@@ -60,7 +60,7 @@ func RegisterTool(s *server.MCPServer) {
s.AddTool(ListMyReposTool, ListMyReposFn)
// File
s.AddTool(GetFileTool, GetFileFn)
s.AddTool(GetFileContentTool, GetFileContentFn)
s.AddTool(CreateFileTool, CreateFileFn)
s.AddTool(UpdateFileTool, UpdateFileFn)
s.AddTool(DeleteFileTool, DeleteFileFn)
@@ -78,7 +78,7 @@ func CreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
log.Debugf("Called CreateRepoFn")
name, ok := req.Params.Arguments["name"].(string)
if !ok {
return nil, errors.New("repository name is required")
return to.ErrorResult(errors.New("repository name is required"))
}
description, _ := req.Params.Arguments["description"].(string)
private, _ := req.Params.Arguments["private"].(bool)
@@ -104,7 +104,7 @@ func CreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
}
repo, _, err := gitea.Client().CreateRepo(opt)
if err != nil {
return nil, err
return to.ErrorResult(fmt.Errorf("create repo err: %v", err))
}
return to.TextResult(repo)
}
@@ -113,21 +113,29 @@ func ForkRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResu
log.Debugf("Called ForkRepoFn")
user, ok := req.Params.Arguments["user"].(string)
if !ok {
return nil, errors.New("user name is required")
return to.ErrorResult(errors.New("user name is required"))
}
repo, ok := req.Params.Arguments["repo"].(string)
if !ok {
return nil, errors.New("repository name is required")
return to.ErrorResult(errors.New("repository name is required"))
}
organization, ok := req.Params.Arguments["organization"].(string)
organizationPtr := ptr.To(organization)
if !ok || organization == "" {
organizationPtr = nil
}
name, ok := req.Params.Arguments["name"].(string)
namePtr := ptr.To(name)
if !ok || name == "" {
namePtr = nil
}
organization, _ := req.Params.Arguments["organization"].(string)
name, _ := req.Params.Arguments["name"].(string)
opt := gitea_sdk.CreateForkOption{
Organization: ptr.To(organization),
Name: ptr.To(name),
Organization: organizationPtr,
Name: namePtr,
}
_, _, err := gitea.Client().CreateFork(user, repo, opt)
if err != nil {
return nil, fmt.Errorf("fork repository error %v", err)
return to.ErrorResult(fmt.Errorf("fork repository error %v", err))
}
return to.TextResult("Fork success")
}
@@ -136,21 +144,21 @@ func ListMyReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
log.Debugf("Called ListMyReposFn")
page, ok := req.Params.Arguments["page"].(float64)
if !ok {
return nil, errors.New("get page number error")
page = 1
}
size, ok := req.Params.Arguments["pageSize"].(float64)
pageSize, ok := req.Params.Arguments["pageSize"].(float64)
if !ok {
return nil, errors.New("get page size number error")
pageSize = 100
}
opt := gitea_sdk.ListReposOptions{
ListOptions: gitea_sdk.ListOptions{
Page: int(page),
PageSize: int(size),
PageSize: int(pageSize),
},
}
repos, _, err := gitea.Client().ListMyRepos(opt)
if err != nil {
return nil, fmt.Errorf("list my repositories error %v", err)
return to.ErrorResult(fmt.Errorf("list my repositories error: %v", err))
}
return to.TextResult(repos)

View File

@@ -65,7 +65,7 @@ func SearchUsersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
log.Debugf("Called SearchUsersFn")
keyword, ok := req.Params.Arguments["keyword"].(string)
if !ok {
return nil, fmt.Errorf("keyword is required")
return to.ErrorResult(fmt.Errorf("keyword is required"))
}
page, ok := req.Params.Arguments["page"].(float64)
if !ok {
@@ -84,7 +84,7 @@ func SearchUsersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
}
users, _, err := gitea.Client().SearchUsers(opt)
if err != nil {
return nil, err
return to.ErrorResult(fmt.Errorf("search users err: %v", err))
}
return to.TextResult(users)
}
@@ -93,11 +93,11 @@ func SearchOrgTeamsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
log.Debugf("Called SearchOrgTeamsFn")
org, ok := req.Params.Arguments["org"].(string)
if !ok {
return nil, fmt.Errorf("organization is required")
return to.ErrorResult(fmt.Errorf("organization is required"))
}
query, ok := req.Params.Arguments["query"].(string)
if !ok {
return nil, fmt.Errorf("query is required")
return to.ErrorResult(fmt.Errorf("query is required"))
}
includeDescription, _ := req.Params.Arguments["includeDescription"].(bool)
page, ok := req.Params.Arguments["page"].(float64)
@@ -118,7 +118,7 @@ func SearchOrgTeamsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
}
teams, _, err := gitea.Client().SearchOrgTeams(org, &opt)
if err != nil {
return nil, fmt.Errorf("search organization teams error: %v", err)
return to.ErrorResult(fmt.Errorf("search organization teams error: %v", err))
}
return to.TextResult(teams)
}
@@ -127,7 +127,7 @@ func SearchReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
log.Debugf("Called SearchReposFn")
keyword, ok := req.Params.Arguments["keyword"].(string)
if !ok {
return nil, fmt.Errorf("keyword is required")
return to.ErrorResult(fmt.Errorf("keyword is required"))
}
keywordIsTopic, _ := req.Params.Arguments["keywordIsTopic"].(bool)
keywordInDescription, _ := req.Params.Arguments["keywordInDescription"].(bool)
@@ -160,7 +160,7 @@ func SearchReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
}
repos, _, err := gitea.Client().SearchRepos(opt)
if err != nil {
return nil, fmt.Errorf("search repos error: %v", err)
return to.ErrorResult(fmt.Errorf("search repos error: %v", err))
}
return to.TextResult(repos)
}

View File

@@ -31,7 +31,7 @@ func GetUserInfoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
log.Debugf("Called GetUserInfoFn")
user, _, err := gitea.Client().GetMyUserInfo()
if err != nil {
return nil, fmt.Errorf("get user info err: %v", err)
return to.ErrorResult(fmt.Errorf("get user info err: %v", err))
}
return to.TextResult(user)

View File

@@ -2,8 +2,11 @@ package flag
var (
Host string
Port int
Token string
Version string
Mode string
Debug bool
Insecure bool
Debug bool
)

View File

@@ -1,6 +1,8 @@
package gitea
import (
"crypto/tls"
"net/http"
"sync"
"gitea.com/gitea/gitea-mcp/pkg/flag"
@@ -16,12 +18,26 @@ var (
func Client() *gitea.Client {
clientOnce.Do(func() {
if client == nil {
c, err := gitea.NewClient(flag.Host, gitea.SetToken(flag.Token))
if err != nil {
log.Fatalf("create gitea client err: %v", err)
var err error
if client != nil {
return
}
opts := []gitea.ClientOption{
gitea.SetToken(flag.Token),
}
if flag.Insecure {
httpClient := &http.Client{
Transport: &http.Transport{
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
}
client = c
opts = append(opts, gitea.SetHTTPClient(httpClient))
}
client, err = gitea.NewClient(flag.Host, opts...)
if err != nil {
log.Fatalf("create gitea client err: %v", err)
}
})
return client

View File

@@ -1,6 +1,7 @@
package log
import (
"fmt"
"os"
"sync"
"time"
@@ -8,6 +9,7 @@ import (
"gitea.com/gitea/gitea-mcp/pkg/flag"
"go.uber.org/zap"
"go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2"
)
var (
@@ -20,12 +22,27 @@ func Default() *zap.Logger {
if defaultLogger == nil {
ec := zap.NewProductionEncoderConfig()
ec.EncodeTime = zapcore.TimeEncoderOfLayout(time.DateTime)
ec.EncodeLevel = zapcore.CapitalColorLevelEncoder
ec.EncodeLevel = zapcore.CapitalLevelEncoder
var ws zapcore.WriteSyncer
var wss []zapcore.WriteSyncer
wss = append(wss, zapcore.AddSync(os.Stdout))
home, _ := os.UserHomeDir()
if home == "" {
home = os.TempDir()
}
wss = append(wss, zapcore.AddSync(&lumberjack.Logger{
Filename: fmt.Sprintf("%s/.gitea-mcp/gitea-mcp.log", home),
MaxSize: 100,
MaxBackups: 10,
MaxAge: 30,
}))
if flag.Mode == "sse" {
wss = append(wss, zapcore.AddSync(os.Stdout))
}
ws = zapcore.NewMultiWriteSyncer(wss...)
enc := zapcore.NewConsoleEncoder(ec)
@@ -37,7 +54,7 @@ func Default() *zap.Logger {
}
core := zapcore.NewCore(enc, ws, level)
options := []zap.Option{
zap.AddStacktrace(zapcore.ErrorLevel),
zap.AddStacktrace(zapcore.DPanicLevel),
zap.AddCaller(),
zap.AddCallerSkip(1),
}

View File

@@ -21,3 +21,8 @@ func TextResult(v any) (*mcp.CallToolResult, error) {
log.Debugf("Text Result: %s", string(resultBytes))
return mcp.NewToolResultText(string(resultBytes)), nil
}
func ErrorResult(err error) (*mcp.CallToolResult, error) {
log.Errorf(err.Error())
return nil, err
}