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