Compare commits
15 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7f38f6a549 | ||
|
|
5dbfe21127 | ||
|
|
b85a523983 | ||
|
|
da08718e24 | ||
|
|
44ea8969f4 | ||
|
|
94aa8dc572 | ||
|
|
05194ffc1c | ||
|
|
5c329129f8 | ||
|
|
52ccf92761 | ||
|
|
061ea86b0b | ||
|
|
f14b60fe56 | ||
|
|
94782a85b6 | ||
|
|
e94dd26b30 | ||
|
|
da49bdeb96 | ||
|
|
3f61299f72 |
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
.gitignore
vendored
1
.gitignore
vendored
@@ -2,3 +2,4 @@
|
|||||||
gitea-mcp
|
gitea-mcp
|
||||||
gitea-mcp.exe
|
gitea-mcp.exe
|
||||||
*.log
|
*.log
|
||||||
|
tmp
|
||||||
|
|||||||
6
.vscode/mcp.json
vendored
6
.vscode/mcp.json
vendored
@@ -21,7 +21,7 @@
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"servers": {
|
"servers": {
|
||||||
"gitea": {
|
"gitea-mcp-stdio": {
|
||||||
"type": "stdio",
|
"type": "stdio",
|
||||||
"command": "gitea-mcp",
|
"command": "gitea-mcp",
|
||||||
"args": ["-t", "stdio"],
|
"args": ["-t", "stdio"],
|
||||||
@@ -30,6 +30,10 @@
|
|||||||
"GITEA_ACCESS_TOKEN": "${input:gitea-token}",
|
"GITEA_ACCESS_TOKEN": "${input:gitea-token}",
|
||||||
"GITEA_INSECURE": "${input:gitea-insecure}"
|
"GITEA_INSECURE": "${input:gitea-insecure}"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"gitea-mcp-http": {
|
||||||
|
"type": "http",
|
||||||
|
"url": "http://localhost:8080/mcp",
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
39
Dockerfile
39
Dockerfile
@@ -1,39 +1,32 @@
|
|||||||
|
# syntax=docker/dockerfile:1.4
|
||||||
|
|
||||||
# Build stage
|
# Build stage
|
||||||
FROM golang:1.24-bullseye AS builder
|
FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder
|
||||||
|
|
||||||
ARG VERSION
|
ARG VERSION=dev
|
||||||
|
ARG TARGETOS
|
||||||
|
ARG TARGETARCH
|
||||||
|
|
||||||
# Set the working directory
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
# Copy go.mod and go.sum files
|
|
||||||
COPY go.mod go.sum ./
|
COPY go.mod go.sum ./
|
||||||
|
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||||
|
go mod download
|
||||||
|
|
||||||
# Download dependencies
|
|
||||||
RUN go mod download
|
|
||||||
|
|
||||||
# Copy the source code
|
|
||||||
COPY . .
|
COPY . .
|
||||||
|
RUN --mount=type=cache,target=/go/pkg/mod \
|
||||||
RUN CGO_ENABLED=0 go build -ldflags="-s -w -X main.Version=${VERSION}" -o gitea-mcp
|
--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
|
# Final stage
|
||||||
FROM debian:bullseye-slim
|
FROM gcr.io/distroless/static-debian12:nonroot
|
||||||
|
|
||||||
ENV GITEA_MODE=stdio
|
|
||||||
|
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
COPY --from=builder --chown=nonroot:nonroot /app/gitea-mcp .
|
||||||
|
|
||||||
# Install ca-certificates for HTTPS requests
|
USER nonroot:nonroot
|
||||||
RUN apt-get update && \
|
|
||||||
apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
|
|
||||||
|
|
||||||
# Create a non-root user
|
LABEL org.opencontainers.image.version="${VERSION}"
|
||||||
RUN useradd -r -u 1000 -m gitea-mcp
|
|
||||||
|
|
||||||
COPY --from=builder --chown=1000:1000 /app/gitea-mcp .
|
|
||||||
|
|
||||||
# Use the non-root user
|
|
||||||
USER gitea-mcp
|
|
||||||
|
|
||||||
CMD ["/app/gitea-mcp"]
|
CMD ["/app/gitea-mcp"]
|
||||||
@@ -54,7 +54,7 @@ Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"servers": {
|
"servers": {
|
||||||
"github": {
|
"gitea-mcp": {
|
||||||
"command": "docker",
|
"command": "docker",
|
||||||
"args": [
|
"args": [
|
||||||
"run",
|
"run",
|
||||||
@@ -194,6 +194,7 @@ The Gitea MCP Server supports the following tools:
|
|||||||
| list_tags | Tag | List all tags in a repository |
|
| list_tags | Tag | List all tags in a repository |
|
||||||
| list_repo_commits | Commit | List all commits in a repository |
|
| list_repo_commits | Commit | List all commits in a repository |
|
||||||
| get_file_content | File | Get the content and metadata of a file |
|
| get_file_content | File | Get the content and metadata of a file |
|
||||||
|
| get_dir_content | File | Get a list of entries in a directory |
|
||||||
| create_file | File | Create a new file |
|
| create_file | File | Create a new file |
|
||||||
| update_file | File | Update an existing file |
|
| update_file | File | Update an existing file |
|
||||||
| delete_file | File | Delete a file |
|
| delete_file | File | Delete a file |
|
||||||
@@ -202,6 +203,8 @@ The Gitea MCP Server supports the following tools:
|
|||||||
| create_issue | Issue | Create a new issue |
|
| create_issue | Issue | Create a new issue |
|
||||||
| create_issue_comment | Issue | Create a comment on an issue |
|
| create_issue_comment | Issue | Create a comment on an issue |
|
||||||
| edit_issue | Issue | Edit a issue |
|
| edit_issue | Issue | Edit a issue |
|
||||||
|
| edit_issue_comment | Issue | Edit a comment on an issue |
|
||||||
|
| get_issue_comments_by_index | Issue | Get comments of an issue by its index |
|
||||||
| get_pull_request_by_index | Pull Request | Get a pull request by its index |
|
| get_pull_request_by_index | Pull Request | Get a pull request by its index |
|
||||||
| list_repo_pull_requests | Pull Request | List all pull requests in a repository |
|
| list_repo_pull_requests | Pull Request | List all pull requests in a repository |
|
||||||
| create_pull_request | Pull Request | Create a new pull request |
|
| create_pull_request | Pull Request | Create a new pull request |
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ Model Context Protocol (MCP) 是一种协议,允许通过聊天界面整合各
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"servers": {
|
"servers": {
|
||||||
"github": {
|
"gitea-mcp": {
|
||||||
"command": "docker",
|
"command": "docker",
|
||||||
"args": [
|
"args": [
|
||||||
"run",
|
"run",
|
||||||
@@ -194,6 +194,7 @@ Gitea MCP 服务器支持以下工具:
|
|||||||
| list_tags | 标签 | 列出所有标签 |
|
| list_tags | 标签 | 列出所有标签 |
|
||||||
| list_repo_commits | 提交 | 列出仓库中的所有提交 |
|
| list_repo_commits | 提交 | 列出仓库中的所有提交 |
|
||||||
| get_file_content | 文件 | 获取文件的内容和元数据 |
|
| get_file_content | 文件 | 获取文件的内容和元数据 |
|
||||||
|
| get_dir_content | 文件 | 获取目录的内容列表 |
|
||||||
| create_file | 文件 | 创建一个新文件 |
|
| create_file | 文件 | 创建一个新文件 |
|
||||||
| update_file | 文件 | 更新现有文件 |
|
| update_file | 文件 | 更新现有文件 |
|
||||||
| delete_file | 文件 | 删除一个文件 |
|
| delete_file | 文件 | 删除一个文件 |
|
||||||
@@ -202,6 +203,8 @@ Gitea MCP 服务器支持以下工具:
|
|||||||
| create_issue | 问题 | 创建一个新问题 |
|
| create_issue | 问题 | 创建一个新问题 |
|
||||||
| create_issue_comment | 问题 | 在问题上创建评论 |
|
| create_issue_comment | 问题 | 在问题上创建评论 |
|
||||||
| edit_issue | 问题 | 编辑一个问题 |
|
| edit_issue | 问题 | 编辑一个问题 |
|
||||||
|
| edit_issue_comment | 问题 | 在问题上编辑评论 |
|
||||||
|
| get_issue_comments_by_index | 问题 | 根据索引获取问题的评论 |
|
||||||
| get_pull_request_by_index | 拉取请求 | 根据索引获取拉取请求 |
|
| get_pull_request_by_index | 拉取请求 | 根据索引获取拉取请求 |
|
||||||
| list_repo_pull_requests | 拉取请求 | 列出仓库中的所有拉取请求 |
|
| list_repo_pull_requests | 拉取请求 | 列出仓库中的所有拉取请求 |
|
||||||
| create_pull_request | 拉取请求 | 创建一个新拉取请求 |
|
| create_pull_request | 拉取请求 | 创建一个新拉取请求 |
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ Model Context Protocol (MCP) 是一種協議,允許通過聊天界面整合各
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
"servers": {
|
"servers": {
|
||||||
"github": {
|
"gitea-mcp": {
|
||||||
"command": "docker",
|
"command": "docker",
|
||||||
"args": [
|
"args": [
|
||||||
"run",
|
"run",
|
||||||
@@ -194,6 +194,7 @@ Gitea MCP 伺服器支持以下工具:
|
|||||||
| list_tags | 標籤 | 列出所有標籤 |
|
| list_tags | 標籤 | 列出所有標籤 |
|
||||||
| list_repo_commits | 提交 | 列出倉庫中的所有提交 |
|
| list_repo_commits | 提交 | 列出倉庫中的所有提交 |
|
||||||
| get_file_content | 文件 | 獲取文件的內容和元數據 |
|
| get_file_content | 文件 | 獲取文件的內容和元數據 |
|
||||||
|
| get_dir_content | 文件 | 獲取目錄的內容列表 |
|
||||||
| create_file | 文件 | 創建一個新文件 |
|
| create_file | 文件 | 創建一個新文件 |
|
||||||
| update_file | 文件 | 更新現有文件 |
|
| update_file | 文件 | 更新現有文件 |
|
||||||
| delete_file | 文件 | 刪除一個文件 |
|
| delete_file | 文件 | 刪除一個文件 |
|
||||||
@@ -202,6 +203,8 @@ Gitea MCP 伺服器支持以下工具:
|
|||||||
| create_issue | 問題 | 創建一個新問題 |
|
| create_issue | 問題 | 創建一個新問題 |
|
||||||
| create_issue_comment | 問題 | 在問題上創建評論 |
|
| create_issue_comment | 問題 | 在問題上創建評論 |
|
||||||
| edit_issue | 問題 | 編輯一個問題 |
|
| edit_issue | 問題 | 編輯一個問題 |
|
||||||
|
| edit_issue_comment | 問題 | 在問題上編輯評論 |
|
||||||
|
| get_issue_comments_by_index | 问题 | 根據索引獲取問題的評論 |
|
||||||
| get_pull_request_by_index | 拉取請求 | 根據索引獲取拉取請求 |
|
| get_pull_request_by_index | 拉取請求 | 根據索引獲取拉取請求 |
|
||||||
| list_repo_pull_requests | 拉取請求 | 列出倉庫中的所有拉取請求 |
|
| list_repo_pull_requests | 拉取請求 | 列出倉庫中的所有拉取請求 |
|
||||||
| create_pull_request | 拉取請求 | 創建一個新拉取請求 |
|
| create_pull_request | 拉取請求 | 創建一個新拉取請求 |
|
||||||
|
|||||||
@@ -80,8 +80,8 @@ func init() {
|
|||||||
flagPkg.Token = os.Getenv("GITEA_ACCESS_TOKEN")
|
flagPkg.Token = os.Getenv("GITEA_ACCESS_TOKEN")
|
||||||
}
|
}
|
||||||
|
|
||||||
if os.Getenv("GITEA_MODE") != "" {
|
if os.Getenv("MCP_MODE") != "" {
|
||||||
flagPkg.Mode = os.Getenv("GITEA_MODE")
|
flagPkg.Mode = os.Getenv("MCP_MODE")
|
||||||
}
|
}
|
||||||
|
|
||||||
if os.Getenv("GITEA_READONLY") == "true" {
|
if os.Getenv("GITEA_READONLY") == "true" {
|
||||||
|
|||||||
2
go.mod
2
go.mod
@@ -4,7 +4,7 @@ go 1.24.0
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
code.gitea.io/sdk/gitea v0.21.0
|
code.gitea.io/sdk/gitea v0.21.0
|
||||||
github.com/mark3labs/mcp-go v0.30.0
|
github.com/mark3labs/mcp-go v0.32.0
|
||||||
go.uber.org/zap v1.27.0
|
go.uber.org/zap v1.27.0
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||||
)
|
)
|
||||||
|
|||||||
4
go.sum
4
go.sum
@@ -20,8 +20,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
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 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/mark3labs/mcp-go v0.30.0 h1:Taz7fiefkxY/l8jz1nA90V+WdM2eoMtlvwfWforVYbo=
|
github.com/mark3labs/mcp-go v0.32.0 h1:fgwmbfL2gbd67obg57OfV2Dnrhs1HtSdlY/i5fn7MU8=
|
||||||
github.com/mark3labs/mcp-go v0.30.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
|
github.com/mark3labs/mcp-go v0.32.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
|
|||||||
@@ -23,6 +23,8 @@ const (
|
|||||||
CreateIssueToolName = "create_issue"
|
CreateIssueToolName = "create_issue"
|
||||||
CreateIssueCommentToolName = "create_issue_comment"
|
CreateIssueCommentToolName = "create_issue_comment"
|
||||||
EditIssueToolName = "edit_issue"
|
EditIssueToolName = "edit_issue"
|
||||||
|
EditIssueCommentToolName = "edit_issue_comment"
|
||||||
|
GetIssueCommentsByIndexToolName = "get_issue_comments_by_index"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var (
|
||||||
@@ -52,6 +54,7 @@ var (
|
|||||||
mcp.WithString("title", mcp.Required(), mcp.Description("issue title")),
|
mcp.WithString("title", mcp.Required(), mcp.Description("issue title")),
|
||||||
mcp.WithString("body", mcp.Required(), mcp.Description("issue body")),
|
mcp.WithString("body", mcp.Required(), mcp.Description("issue body")),
|
||||||
)
|
)
|
||||||
|
|
||||||
CreateIssueCommentTool = mcp.NewTool(
|
CreateIssueCommentTool = mcp.NewTool(
|
||||||
CreateIssueCommentToolName,
|
CreateIssueCommentToolName,
|
||||||
mcp.WithDescription("create issue comment"),
|
mcp.WithDescription("create issue comment"),
|
||||||
@@ -60,6 +63,7 @@ var (
|
|||||||
mcp.WithNumber("index", mcp.Required(), mcp.Description("repository issue index")),
|
mcp.WithNumber("index", mcp.Required(), mcp.Description("repository issue index")),
|
||||||
mcp.WithString("body", mcp.Required(), mcp.Description("issue comment body")),
|
mcp.WithString("body", mcp.Required(), mcp.Description("issue comment body")),
|
||||||
)
|
)
|
||||||
|
|
||||||
EditIssueTool = mcp.NewTool(
|
EditIssueTool = mcp.NewTool(
|
||||||
EditIssueToolName,
|
EditIssueToolName,
|
||||||
mcp.WithDescription("edit issue"),
|
mcp.WithDescription("edit issue"),
|
||||||
@@ -72,6 +76,23 @@ var (
|
|||||||
mcp.WithNumber("milestone", mcp.Description("milestone number")),
|
mcp.WithNumber("milestone", mcp.Description("milestone number")),
|
||||||
mcp.WithString("state", mcp.Description("issue state, one of open, closed, all")),
|
mcp.WithString("state", mcp.Description("issue state, one of open, closed, all")),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
EditIssueCommentTool = mcp.NewTool(
|
||||||
|
EditIssueCommentToolName,
|
||||||
|
mcp.WithDescription("edit issue comment"),
|
||||||
|
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
||||||
|
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
||||||
|
mcp.WithNumber("commentID", mcp.Required(), mcp.Description("id of issue comment")),
|
||||||
|
mcp.WithString("body", mcp.Required(), mcp.Description("issue comment body")),
|
||||||
|
)
|
||||||
|
|
||||||
|
GetIssueCommentsByIndexTool = mcp.NewTool(
|
||||||
|
GetIssueCommentsByIndexToolName,
|
||||||
|
mcp.WithDescription("get issue comment by index"),
|
||||||
|
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
||||||
|
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
||||||
|
mcp.WithNumber("index", mcp.Required(), mcp.Description("repository issue index")),
|
||||||
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
@@ -95,6 +116,14 @@ func init() {
|
|||||||
Tool: EditIssueTool,
|
Tool: EditIssueTool,
|
||||||
Handler: EditIssueFn,
|
Handler: EditIssueFn,
|
||||||
})
|
})
|
||||||
|
Tool.RegisterWrite(server.ServerTool{
|
||||||
|
Tool: EditIssueCommentTool,
|
||||||
|
Handler: EditIssueCommentFn,
|
||||||
|
})
|
||||||
|
Tool.RegisterRead(server.ServerTool{
|
||||||
|
Tool: GetIssueCommentsByIndexTool,
|
||||||
|
Handler: GetIssueCommentsByIndexFn,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetIssueByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
func GetIssueByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||||
@@ -258,3 +287,55 @@ func EditIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRes
|
|||||||
|
|
||||||
return to.TextResult(issue)
|
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)
|
||||||
|
}
|
||||||
|
|||||||
455
operation/label/label.go
Normal file
455
operation/label/label.go
Normal file
@@ -0,0 +1,455 @@
|
|||||||
|
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/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 (
|
||||||
|
CreateLabelToolName = "create_label"
|
||||||
|
GetRepoLabelToolName = "get_repo_label"
|
||||||
|
ListRepoLabelsToolName = "list_repo_labels"
|
||||||
|
EditLabelToolName = "edit_label"
|
||||||
|
DeleteLabelToolName = "delete_label"
|
||||||
|
GetIssueLabelsToolName = "get_issue_labels"
|
||||||
|
AddIssueLabelsToolName = "add_issue_labels"
|
||||||
|
RemoveIssueLabelsToolName = "remove_issue_labels"
|
||||||
|
ReplaceIssueLabelsToolName = "replace_issue_labels"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
CreateLabelTool = mcp.NewTool(
|
||||||
|
CreateLabelToolName,
|
||||||
|
mcp.WithDescription("Create label"),
|
||||||
|
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 in hex format (without #)")),
|
||||||
|
mcp.WithString("description", mcp.Description("label description")),
|
||||||
|
)
|
||||||
|
|
||||||
|
GetRepoLabelTool = mcp.NewTool(
|
||||||
|
GetRepoLabelToolName,
|
||||||
|
mcp.WithDescription("Get repository label by ID"),
|
||||||
|
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")),
|
||||||
|
)
|
||||||
|
|
||||||
|
ListRepoLabelsTool = mcp.NewTool(
|
||||||
|
ListRepoLabelsToolName,
|
||||||
|
mcp.WithDescription("List repository labels"),
|
||||||
|
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("limit", mcp.Description("page size"), mcp.DefaultNumber(50), mcp.Min(1)),
|
||||||
|
)
|
||||||
|
|
||||||
|
EditLabelTool = mcp.NewTool(
|
||||||
|
EditLabelToolName,
|
||||||
|
mcp.WithDescription("Edit label"),
|
||||||
|
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("label name")),
|
||||||
|
mcp.WithString("color", mcp.Description("label color in hex format (without #)")),
|
||||||
|
mcp.WithString("description", mcp.Description("label description")),
|
||||||
|
)
|
||||||
|
|
||||||
|
DeleteLabelTool = mcp.NewTool(
|
||||||
|
DeleteLabelToolName,
|
||||||
|
mcp.WithDescription("Delete label"),
|
||||||
|
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")),
|
||||||
|
)
|
||||||
|
|
||||||
|
GetIssueLabelsTool = mcp.NewTool(
|
||||||
|
GetIssueLabelsToolName,
|
||||||
|
mcp.WithDescription("Get labels for 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")),
|
||||||
|
)
|
||||||
|
|
||||||
|
AddIssueLabelsTool = mcp.NewTool(
|
||||||
|
AddIssueLabelsToolName,
|
||||||
|
mcp.WithDescription("Add 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"})),
|
||||||
|
)
|
||||||
|
|
||||||
|
RemoveIssueLabelsTool = mcp.NewTool(
|
||||||
|
RemoveIssueLabelsToolName,
|
||||||
|
mcp.WithDescription("Remove 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")),
|
||||||
|
mcp.WithArray("labels", mcp.Required(), mcp.Description("array of label IDs to remove"), mcp.Items(map[string]interface{}{"type": "number"})),
|
||||||
|
)
|
||||||
|
|
||||||
|
ReplaceIssueLabelsTool = mcp.NewTool(
|
||||||
|
ReplaceIssueLabelsToolName,
|
||||||
|
mcp.WithDescription("Replace 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 set"), mcp.Items(map[string]interface{}{"type": "number"})),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Tool.RegisterWrite(server.ServerTool{
|
||||||
|
Tool: CreateLabelTool,
|
||||||
|
Handler: CreateLabelFn,
|
||||||
|
})
|
||||||
|
Tool.RegisterRead(server.ServerTool{
|
||||||
|
Tool: GetRepoLabelTool,
|
||||||
|
Handler: GetRepoLabelFn,
|
||||||
|
})
|
||||||
|
Tool.RegisterRead(server.ServerTool{
|
||||||
|
Tool: ListRepoLabelsTool,
|
||||||
|
Handler: ListRepoLabelsFn,
|
||||||
|
})
|
||||||
|
Tool.RegisterWrite(server.ServerTool{
|
||||||
|
Tool: EditLabelTool,
|
||||||
|
Handler: EditLabelFn,
|
||||||
|
})
|
||||||
|
Tool.RegisterWrite(server.ServerTool{
|
||||||
|
Tool: DeleteLabelTool,
|
||||||
|
Handler: DeleteLabelFn,
|
||||||
|
})
|
||||||
|
Tool.RegisterRead(server.ServerTool{
|
||||||
|
Tool: GetIssueLabelsTool,
|
||||||
|
Handler: GetIssueLabelsFn,
|
||||||
|
})
|
||||||
|
Tool.RegisterWrite(server.ServerTool{
|
||||||
|
Tool: AddIssueLabelsTool,
|
||||||
|
Handler: AddIssueLabelsFn,
|
||||||
|
})
|
||||||
|
Tool.RegisterWrite(server.ServerTool{
|
||||||
|
Tool: RemoveIssueLabelsTool,
|
||||||
|
Handler: RemoveIssueLabelsFn,
|
||||||
|
})
|
||||||
|
Tool.RegisterWrite(server.ServerTool{
|
||||||
|
Tool: ReplaceIssueLabelsTool,
|
||||||
|
Handler: ReplaceIssueLabelsFn,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||||
|
log.Debugf("Called CreateLabelFn")
|
||||||
|
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"))
|
||||||
|
}
|
||||||
|
|
||||||
|
opt := gitea_sdk.CreateLabelOption{
|
||||||
|
Name: name,
|
||||||
|
Color: color,
|
||||||
|
}
|
||||||
|
|
||||||
|
if description, ok := req.GetArguments()["description"].(string); ok {
|
||||||
|
opt.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 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("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 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
|
||||||
|
}
|
||||||
|
limit, ok := req.GetArguments()["limit"].(float64)
|
||||||
|
if !ok {
|
||||||
|
limit = 50
|
||||||
|
}
|
||||||
|
|
||||||
|
opt := gitea_sdk.ListLabelsOptions{
|
||||||
|
ListOptions: gitea_sdk.ListOptions{
|
||||||
|
Page: int(page),
|
||||||
|
PageSize: int(limit),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
labels, _, err := gitea.Client().ListRepoLabels(owner, repo, opt)
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(fmt.Errorf("get %v/%v/labels err: %v", owner, repo, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return to.TextResult(labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
func EditLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||||
|
log.Debugf("Called EditLabelFn")
|
||||||
|
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("id is required"))
|
||||||
|
}
|
||||||
|
|
||||||
|
opt := gitea_sdk.EditLabelOption{}
|
||||||
|
|
||||||
|
if name, ok := req.GetArguments()["name"].(string); ok {
|
||||||
|
opt.Name = &name
|
||||||
|
}
|
||||||
|
if color, ok := req.GetArguments()["color"].(string); ok {
|
||||||
|
opt.Color = &color
|
||||||
|
}
|
||||||
|
if description, ok := req.GetArguments()["description"].(string); ok {
|
||||||
|
opt.Description = &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 DeleteLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||||
|
log.Debugf("Called DeleteLabelFn")
|
||||||
|
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("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(map[string]interface{}{
|
||||||
|
"success": true,
|
||||||
|
"message": fmt.Sprintf("Label %d deleted successfully", int64(id)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||||
|
log.Debugf("Called GetIssueLabelsFn")
|
||||||
|
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"))
|
||||||
|
}
|
||||||
|
|
||||||
|
labels, _, err := gitea.Client().GetIssueLabels(owner, repo, int64(index), gitea_sdk.ListLabelsOptions{})
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(fmt.Errorf("get %v/%v/issues/%v/labels err: %v", owner, repo, int64(index), err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return to.TextResult(labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
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("index is required"))
|
||||||
|
}
|
||||||
|
labelsData, ok := req.GetArguments()["labels"].([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return to.ErrorResult(fmt.Errorf("labels is required"))
|
||||||
|
}
|
||||||
|
|
||||||
|
labelIDs := make([]int64, len(labelsData))
|
||||||
|
for i, labelData := range labelsData {
|
||||||
|
if labelID, ok := labelData.(float64); ok {
|
||||||
|
labelIDs[i] = int64(labelID)
|
||||||
|
} else {
|
||||||
|
return to.ErrorResult(fmt.Errorf("invalid label ID at index %d", i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
opt := gitea_sdk.IssueLabelsOption{
|
||||||
|
Labels: labelIDs,
|
||||||
|
}
|
||||||
|
|
||||||
|
labels, _, err := gitea.Client().AddIssueLabels(owner, repo, int64(index), opt)
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(fmt.Errorf("add labels to %v/%v/issues/%v err: %v", owner, repo, int64(index), err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return to.TextResult(labels)
|
||||||
|
}
|
||||||
|
|
||||||
|
func RemoveIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||||
|
log.Debugf("Called RemoveIssueLabelsFn")
|
||||||
|
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"))
|
||||||
|
}
|
||||||
|
labelsData, ok := req.GetArguments()["labels"].([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return to.ErrorResult(fmt.Errorf("labels is required"))
|
||||||
|
}
|
||||||
|
|
||||||
|
labelIDs := make([]int64, len(labelsData))
|
||||||
|
for i, labelData := range labelsData {
|
||||||
|
if labelID, ok := labelData.(float64); ok {
|
||||||
|
labelIDs[i] = int64(labelID)
|
||||||
|
} else {
|
||||||
|
return to.ErrorResult(fmt.Errorf("invalid label ID at index %d", i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var errors []string
|
||||||
|
for _, labelID := range labelIDs {
|
||||||
|
_, err := gitea.Client().DeleteIssueLabel(owner, repo, int64(index), labelID)
|
||||||
|
if err != nil {
|
||||||
|
errors = append(errors, fmt.Sprintf("failed to remove label %d: %v", labelID, err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(errors) > 0 {
|
||||||
|
return to.ErrorResult(fmt.Errorf("remove labels from %v/%v/issues/%v errors: %v", owner, repo, int64(index), errors))
|
||||||
|
}
|
||||||
|
|
||||||
|
return to.TextResult(map[string]interface{}{
|
||||||
|
"success": true,
|
||||||
|
"message": fmt.Sprintf("Labels removed from issue %d successfully", int64(index)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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("index is required"))
|
||||||
|
}
|
||||||
|
labelsData, ok := req.GetArguments()["labels"].([]interface{})
|
||||||
|
if !ok {
|
||||||
|
return to.ErrorResult(fmt.Errorf("labels is required"))
|
||||||
|
}
|
||||||
|
|
||||||
|
labelIDs := make([]int64, len(labelsData))
|
||||||
|
for i, labelData := range labelsData {
|
||||||
|
if labelID, ok := labelData.(float64); ok {
|
||||||
|
labelIDs[i] = int64(labelID)
|
||||||
|
} else {
|
||||||
|
return to.ErrorResult(fmt.Errorf("invalid label ID at index %d", i))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
opt := gitea_sdk.IssueLabelsOption{
|
||||||
|
Labels: labelIDs,
|
||||||
|
}
|
||||||
|
|
||||||
|
labels, _, err := gitea.Client().ReplaceIssueLabels(owner, repo, int64(index), opt)
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(fmt.Errorf("replace labels on %v/%v/issues/%v err: %v", owner, repo, int64(index), err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return to.TextResult(labels)
|
||||||
|
}
|
||||||
402
operation/milestone/milestone.go
Normal file
402
operation/milestone/milestone.go
Normal file
@@ -0,0 +1,402 @@
|
|||||||
|
package milestone
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"fmt"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"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 (
|
||||||
|
CreateMilestoneToolName = "create_milestone"
|
||||||
|
GetMilestoneToolName = "get_milestone"
|
||||||
|
GetMilestoneByNameToolName = "get_milestone_by_name"
|
||||||
|
ListRepoMilestonesToolName = "list_repo_milestones"
|
||||||
|
EditMilestoneToolName = "edit_milestone"
|
||||||
|
EditMilestoneByNameToolName = "edit_milestone_by_name"
|
||||||
|
DeleteMilestoneToolName = "delete_milestone"
|
||||||
|
DeleteMilestoneByNameToolName = "delete_milestone_by_name"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
CreateMilestoneTool = mcp.NewTool(
|
||||||
|
CreateMilestoneToolName,
|
||||||
|
mcp.WithDescription("Create milestone"),
|
||||||
|
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
||||||
|
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
||||||
|
mcp.WithString("title", mcp.Required(), mcp.Description("milestone title")),
|
||||||
|
mcp.WithString("description", mcp.Description("milestone description")),
|
||||||
|
mcp.WithString("due_date", mcp.Description("milestone due date in RFC3339 format")),
|
||||||
|
mcp.WithString("state", mcp.Description("milestone state (open or closed)"), mcp.DefaultString("open")),
|
||||||
|
)
|
||||||
|
|
||||||
|
GetMilestoneTool = mcp.NewTool(
|
||||||
|
GetMilestoneToolName,
|
||||||
|
mcp.WithDescription("Get milestone by ID"),
|
||||||
|
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("milestone ID")),
|
||||||
|
)
|
||||||
|
|
||||||
|
GetMilestoneByNameTool = mcp.NewTool(
|
||||||
|
GetMilestoneByNameToolName,
|
||||||
|
mcp.WithDescription("Get milestone by name"),
|
||||||
|
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("milestone name")),
|
||||||
|
)
|
||||||
|
|
||||||
|
ListRepoMilestonesTool = mcp.NewTool(
|
||||||
|
ListRepoMilestonesToolName,
|
||||||
|
mcp.WithDescription("List repository milestones"),
|
||||||
|
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
||||||
|
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
||||||
|
mcp.WithString("state", mcp.Description("milestone state (open, closed, all)"), mcp.DefaultString("open")),
|
||||||
|
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)),
|
||||||
|
mcp.WithNumber("limit", mcp.Description("page size"), mcp.DefaultNumber(10), mcp.Min(1)),
|
||||||
|
)
|
||||||
|
|
||||||
|
EditMilestoneTool = mcp.NewTool(
|
||||||
|
EditMilestoneToolName,
|
||||||
|
mcp.WithDescription("Edit milestone by ID"),
|
||||||
|
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("milestone ID")),
|
||||||
|
mcp.WithString("title", mcp.Description("milestone title")),
|
||||||
|
mcp.WithString("description", mcp.Description("milestone description")),
|
||||||
|
mcp.WithString("due_date", mcp.Description("milestone due date in RFC3339 format")),
|
||||||
|
mcp.WithString("state", mcp.Description("milestone state (open or closed)")),
|
||||||
|
)
|
||||||
|
|
||||||
|
EditMilestoneByNameTool = mcp.NewTool(
|
||||||
|
EditMilestoneByNameToolName,
|
||||||
|
mcp.WithDescription("Edit milestone by name"),
|
||||||
|
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("milestone name")),
|
||||||
|
mcp.WithString("title", mcp.Description("milestone title")),
|
||||||
|
mcp.WithString("description", mcp.Description("milestone description")),
|
||||||
|
mcp.WithString("due_date", mcp.Description("milestone due date in RFC3339 format")),
|
||||||
|
mcp.WithString("state", mcp.Description("milestone state (open or closed)")),
|
||||||
|
)
|
||||||
|
|
||||||
|
DeleteMilestoneTool = mcp.NewTool(
|
||||||
|
DeleteMilestoneToolName,
|
||||||
|
mcp.WithDescription("Delete milestone by ID"),
|
||||||
|
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("milestone ID")),
|
||||||
|
)
|
||||||
|
|
||||||
|
DeleteMilestoneByNameTool = mcp.NewTool(
|
||||||
|
DeleteMilestoneByNameToolName,
|
||||||
|
mcp.WithDescription("Delete milestone by name"),
|
||||||
|
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("milestone name")),
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
Tool.RegisterWrite(server.ServerTool{
|
||||||
|
Tool: CreateMilestoneTool,
|
||||||
|
Handler: CreateMilestoneFn,
|
||||||
|
})
|
||||||
|
Tool.RegisterRead(server.ServerTool{
|
||||||
|
Tool: GetMilestoneTool,
|
||||||
|
Handler: GetMilestoneFn,
|
||||||
|
})
|
||||||
|
Tool.RegisterRead(server.ServerTool{
|
||||||
|
Tool: GetMilestoneByNameTool,
|
||||||
|
Handler: GetMilestoneByNameFn,
|
||||||
|
})
|
||||||
|
Tool.RegisterRead(server.ServerTool{
|
||||||
|
Tool: ListRepoMilestonesTool,
|
||||||
|
Handler: ListRepoMilestonesFn,
|
||||||
|
})
|
||||||
|
Tool.RegisterWrite(server.ServerTool{
|
||||||
|
Tool: EditMilestoneTool,
|
||||||
|
Handler: EditMilestoneFn,
|
||||||
|
})
|
||||||
|
Tool.RegisterWrite(server.ServerTool{
|
||||||
|
Tool: EditMilestoneByNameTool,
|
||||||
|
Handler: EditMilestoneByNameFn,
|
||||||
|
})
|
||||||
|
Tool.RegisterWrite(server.ServerTool{
|
||||||
|
Tool: DeleteMilestoneTool,
|
||||||
|
Handler: DeleteMilestoneFn,
|
||||||
|
})
|
||||||
|
Tool.RegisterWrite(server.ServerTool{
|
||||||
|
Tool: DeleteMilestoneByNameTool,
|
||||||
|
Handler: DeleteMilestoneByNameFn,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func CreateMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||||
|
log.Debugf("Called CreateMilestoneFn")
|
||||||
|
owner, ok := req.GetArguments()["owner"].(string)
|
||||||
|
if !ok {
|
||||||
|
return to.ErrorResult(fmt.Errorf("owner is required"))
|
||||||
|
}
|
||||||
|
repo, ok := req.GetArguments()["repo"].(string)
|
||||||
|
if !ok {
|
||||||
|
return to.ErrorResult(fmt.Errorf("repo is required"))
|
||||||
|
}
|
||||||
|
title, ok := req.GetArguments()["title"].(string)
|
||||||
|
if !ok {
|
||||||
|
return to.ErrorResult(fmt.Errorf("title is required"))
|
||||||
|
}
|
||||||
|
|
||||||
|
opt := gitea_sdk.CreateMilestoneOption{
|
||||||
|
Title: title,
|
||||||
|
}
|
||||||
|
|
||||||
|
if description, ok := req.GetArguments()["description"].(string); ok {
|
||||||
|
opt.Description = description
|
||||||
|
}
|
||||||
|
if dueDate, ok := req.GetArguments()["due_date"].(string); ok && dueDate != "" {
|
||||||
|
if parsedTime, err := time.Parse(time.RFC3339, dueDate); err == nil {
|
||||||
|
opt.Deadline = &parsedTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if state, ok := req.GetArguments()["state"].(string); ok {
|
||||||
|
opt.State = gitea_sdk.StateType(state)
|
||||||
|
}
|
||||||
|
|
||||||
|
milestone, _, err := gitea.Client().CreateMilestone(owner, repo, opt)
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(fmt.Errorf("create %v/%v/milestone err: %v", owner, repo, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return to.TextResult(milestone)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||||
|
log.Debugf("Called GetMilestoneFn")
|
||||||
|
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("id is required"))
|
||||||
|
}
|
||||||
|
|
||||||
|
milestone, _, err := gitea.Client().GetMilestone(owner, repo, int64(id))
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(fmt.Errorf("get %v/%v/milestone/%v err: %v", owner, repo, int64(id), err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return to.TextResult(milestone)
|
||||||
|
}
|
||||||
|
|
||||||
|
func GetMilestoneByNameFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||||
|
log.Debugf("Called GetMilestoneByNameFn")
|
||||||
|
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"))
|
||||||
|
}
|
||||||
|
|
||||||
|
milestone, _, err := gitea.Client().GetMilestoneByName(owner, repo, name)
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(fmt.Errorf("get %v/%v/milestone/%v err: %v", owner, repo, name, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return to.TextResult(milestone)
|
||||||
|
}
|
||||||
|
|
||||||
|
func ListRepoMilestonesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||||
|
log.Debugf("Called ListRepoMilestonesFn")
|
||||||
|
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"))
|
||||||
|
}
|
||||||
|
|
||||||
|
state, ok := req.GetArguments()["state"].(string)
|
||||||
|
if !ok {
|
||||||
|
state = "open"
|
||||||
|
}
|
||||||
|
page, ok := req.GetArguments()["page"].(float64)
|
||||||
|
if !ok {
|
||||||
|
page = 1
|
||||||
|
}
|
||||||
|
limit, ok := req.GetArguments()["limit"].(float64)
|
||||||
|
if !ok {
|
||||||
|
limit = 10
|
||||||
|
}
|
||||||
|
|
||||||
|
opt := gitea_sdk.ListMilestoneOption{
|
||||||
|
State: gitea_sdk.StateType(state),
|
||||||
|
ListOptions: gitea_sdk.ListOptions{
|
||||||
|
Page: int(page),
|
||||||
|
PageSize: int(limit),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
milestones, _, err := gitea.Client().ListRepoMilestones(owner, repo, opt)
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(fmt.Errorf("get %v/%v/milestones err: %v", owner, repo, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return to.TextResult(milestones)
|
||||||
|
}
|
||||||
|
|
||||||
|
func EditMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||||
|
log.Debugf("Called EditMilestoneFn")
|
||||||
|
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("id is required"))
|
||||||
|
}
|
||||||
|
|
||||||
|
opt := gitea_sdk.EditMilestoneOption{}
|
||||||
|
|
||||||
|
if title, ok := req.GetArguments()["title"].(string); ok {
|
||||||
|
opt.Title = title
|
||||||
|
}
|
||||||
|
if description, ok := req.GetArguments()["description"].(string); ok {
|
||||||
|
opt.Description = &description
|
||||||
|
}
|
||||||
|
if dueDate, ok := req.GetArguments()["due_date"].(string); ok {
|
||||||
|
if parsedTime, err := time.Parse(time.RFC3339, dueDate); err == nil {
|
||||||
|
opt.Deadline = &parsedTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if state, ok := req.GetArguments()["state"].(string); ok {
|
||||||
|
stateType := gitea_sdk.StateType(state)
|
||||||
|
opt.State = &stateType
|
||||||
|
}
|
||||||
|
|
||||||
|
milestone, _, err := gitea.Client().EditMilestone(owner, repo, int64(id), opt)
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(fmt.Errorf("edit %v/%v/milestone/%v err: %v", owner, repo, int64(id), err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return to.TextResult(milestone)
|
||||||
|
}
|
||||||
|
|
||||||
|
func EditMilestoneByNameFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||||
|
log.Debugf("Called EditMilestoneByNameFn")
|
||||||
|
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"))
|
||||||
|
}
|
||||||
|
|
||||||
|
opt := gitea_sdk.EditMilestoneOption{}
|
||||||
|
|
||||||
|
if title, ok := req.GetArguments()["title"].(string); ok {
|
||||||
|
opt.Title = title
|
||||||
|
}
|
||||||
|
if description, ok := req.GetArguments()["description"].(string); ok {
|
||||||
|
opt.Description = &description
|
||||||
|
}
|
||||||
|
if dueDate, ok := req.GetArguments()["due_date"].(string); ok {
|
||||||
|
if parsedTime, err := time.Parse(time.RFC3339, dueDate); err == nil {
|
||||||
|
opt.Deadline = &parsedTime
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if state, ok := req.GetArguments()["state"].(string); ok {
|
||||||
|
stateType := gitea_sdk.StateType(state)
|
||||||
|
opt.State = &stateType
|
||||||
|
}
|
||||||
|
|
||||||
|
milestone, _, err := gitea.Client().EditMilestoneByName(owner, repo, name, opt)
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(fmt.Errorf("edit %v/%v/milestone/%v err: %v", owner, repo, name, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return to.TextResult(milestone)
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||||
|
log.Debugf("Called DeleteMilestoneFn")
|
||||||
|
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("id is required"))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := gitea.Client().DeleteMilestone(owner, repo, int64(id))
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(fmt.Errorf("delete %v/%v/milestone/%v err: %v", owner, repo, int64(id), err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return to.TextResult(map[string]interface{}{
|
||||||
|
"success": true,
|
||||||
|
"message": fmt.Sprintf("Milestone %d deleted successfully", int64(id)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func DeleteMilestoneByNameFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||||
|
log.Debugf("Called DeleteMilestoneByNameFn")
|
||||||
|
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"))
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err := gitea.Client().DeleteMilestoneByName(owner, repo, name)
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(fmt.Errorf("delete %v/%v/milestone/%v err: %v", owner, repo, name, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return to.TextResult(map[string]interface{}{
|
||||||
|
"success": true,
|
||||||
|
"message": fmt.Sprintf("Milestone '%s' deleted successfully", name),
|
||||||
|
})
|
||||||
|
}
|
||||||
@@ -4,6 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"gitea.com/gitea/gitea-mcp/operation/issue"
|
"gitea.com/gitea/gitea-mcp/operation/issue"
|
||||||
|
"gitea.com/gitea/gitea-mcp/operation/label"
|
||||||
|
"gitea.com/gitea/gitea-mcp/operation/milestone"
|
||||||
"gitea.com/gitea/gitea-mcp/operation/pull"
|
"gitea.com/gitea/gitea-mcp/operation/pull"
|
||||||
"gitea.com/gitea/gitea-mcp/operation/repo"
|
"gitea.com/gitea/gitea-mcp/operation/repo"
|
||||||
"gitea.com/gitea/gitea-mcp/operation/search"
|
"gitea.com/gitea/gitea-mcp/operation/search"
|
||||||
@@ -30,6 +32,12 @@ func RegisterTool(s *server.MCPServer) {
|
|||||||
// Pull Tool
|
// Pull Tool
|
||||||
s.AddTools(pull.Tool.Tools()...)
|
s.AddTools(pull.Tool.Tools()...)
|
||||||
|
|
||||||
|
// Milestone Tool
|
||||||
|
s.AddTools(milestone.Tool.Tools()...)
|
||||||
|
|
||||||
|
// Label Tool
|
||||||
|
s.AddTools(label.Tool.Tools()...)
|
||||||
|
|
||||||
// Search Tool
|
// Search Tool
|
||||||
s.AddTools(search.Tool.Tools()...)
|
s.AddTools(search.Tool.Tools()...)
|
||||||
|
|
||||||
@@ -44,17 +52,24 @@ func Run() error {
|
|||||||
RegisterTool(mcpServer)
|
RegisterTool(mcpServer)
|
||||||
switch flag.Mode {
|
switch flag.Mode {
|
||||||
case "stdio":
|
case "stdio":
|
||||||
if err := server.ServeStdio(mcpServer); err != nil {
|
if err := server.ServeStdio(
|
||||||
|
mcpServer,
|
||||||
|
); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case "sse":
|
case "sse":
|
||||||
sseServer := server.NewSSEServer(mcpServer)
|
sseServer := server.NewSSEServer(
|
||||||
|
mcpServer,
|
||||||
|
)
|
||||||
log.Infof("Gitea MCP SSE server listening on :%d", flag.Port)
|
log.Infof("Gitea MCP SSE server listening on :%d", flag.Port)
|
||||||
if err := sseServer.Start(fmt.Sprintf(":%d", flag.Port)); err != nil {
|
if err := sseServer.Start(fmt.Sprintf(":%d", flag.Port)); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
case "http":
|
case "http":
|
||||||
httpServer := server.NewStreamableHTTPServer(mcpServer)
|
httpServer := server.NewStreamableHTTPServer(
|
||||||
|
mcpServer,
|
||||||
|
server.WithLogger(log.New()),
|
||||||
|
)
|
||||||
log.Infof("Gitea MCP HTTP server listening on :%d", flag.Port)
|
log.Infof("Gitea MCP HTTP server listening on :%d", flag.Port)
|
||||||
if err := httpServer.Start(fmt.Sprintf(":%d", flag.Port)); err != nil {
|
if err := httpServer.Start(fmt.Sprintf(":%d", flag.Port)); err != nil {
|
||||||
return err
|
return err
|
||||||
@@ -71,5 +86,6 @@ func newMCPServer(version string) *server.MCPServer {
|
|||||||
version,
|
version,
|
||||||
server.WithToolCapabilities(true),
|
server.WithToolCapabilities(true),
|
||||||
server.WithLogging(),
|
server.WithLogging(),
|
||||||
|
server.WithRecovery(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,6 +16,7 @@ import (
|
|||||||
|
|
||||||
const (
|
const (
|
||||||
GetFileToolName = "get_file_content"
|
GetFileToolName = "get_file_content"
|
||||||
|
GetDirToolName = "get_dir_content"
|
||||||
CreateFileToolName = "create_file"
|
CreateFileToolName = "create_file"
|
||||||
UpdateFileToolName = "update_file"
|
UpdateFileToolName = "update_file"
|
||||||
DeleteFileToolName = "delete_file"
|
DeleteFileToolName = "delete_file"
|
||||||
@@ -31,6 +32,15 @@ var (
|
|||||||
mcp.WithString("filePath", mcp.Required(), mcp.Description("file path")),
|
mcp.WithString("filePath", mcp.Required(), mcp.Description("file path")),
|
||||||
)
|
)
|
||||||
|
|
||||||
|
GetDirContentTool = mcp.NewTool(
|
||||||
|
GetDirToolName,
|
||||||
|
mcp.WithDescription("Get a list of entries in a directory"),
|
||||||
|
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
|
||||||
|
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
|
||||||
|
mcp.WithString("ref", mcp.Required(), mcp.Description("ref can be branch/tag/commit")),
|
||||||
|
mcp.WithString("filePath", mcp.Required(), mcp.Description("directory path")),
|
||||||
|
)
|
||||||
|
|
||||||
CreateFileTool = mcp.NewTool(
|
CreateFileTool = mcp.NewTool(
|
||||||
CreateFileToolName,
|
CreateFileToolName,
|
||||||
mcp.WithDescription("Create file"),
|
mcp.WithDescription("Create file"),
|
||||||
@@ -72,6 +82,10 @@ func init() {
|
|||||||
Tool: GetFileContentTool,
|
Tool: GetFileContentTool,
|
||||||
Handler: GetFileContentFn,
|
Handler: GetFileContentFn,
|
||||||
})
|
})
|
||||||
|
Tool.RegisterRead(server.ServerTool{
|
||||||
|
Tool: GetDirContentTool,
|
||||||
|
Handler: GetDirContentFn,
|
||||||
|
})
|
||||||
Tool.RegisterWrite(server.ServerTool{
|
Tool.RegisterWrite(server.ServerTool{
|
||||||
Tool: CreateFileTool,
|
Tool: CreateFileTool,
|
||||||
Handler: CreateFileFn,
|
Handler: CreateFileFn,
|
||||||
@@ -108,6 +122,28 @@ func GetFileContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
|
|||||||
return to.TextResult(content)
|
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) {
|
func CreateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||||
log.Debugf("Called CreateFileFn")
|
log.Debugf("Called CreateFileFn")
|
||||||
owner, ok := req.GetArguments()["owner"].(string)
|
owner, ok := req.GetArguments()["owner"].(string)
|
||||||
|
|||||||
@@ -19,12 +19,10 @@ const (
|
|||||||
GetGiteaMCPServerVersion = "get_gitea_mcp_server_version"
|
GetGiteaMCPServerVersion = "get_gitea_mcp_server_version"
|
||||||
)
|
)
|
||||||
|
|
||||||
var (
|
var GetGiteaMCPServerVersionTool = mcp.NewTool(
|
||||||
GetGiteaMCPServerVersionTool = mcp.NewTool(
|
|
||||||
GetGiteaMCPServerVersion,
|
GetGiteaMCPServerVersion,
|
||||||
mcp.WithDescription("Get Gitea MCP Server Version"),
|
mcp.WithDescription("Get Gitea MCP Server Version"),
|
||||||
)
|
)
|
||||||
)
|
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
Tool.RegisterRead(server.ServerTool{
|
Tool.RegisterRead(server.ServerTool{
|
||||||
|
|||||||
@@ -22,19 +22,20 @@ func Client() *gitea.Client {
|
|||||||
if client != nil {
|
if client != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
httpClient := &http.Client{
|
||||||
|
Transport: http.DefaultTransport,
|
||||||
|
}
|
||||||
|
|
||||||
opts := []gitea.ClientOption{
|
opts := []gitea.ClientOption{
|
||||||
gitea.SetToken(flag.Token),
|
gitea.SetToken(flag.Token),
|
||||||
}
|
}
|
||||||
if flag.Insecure {
|
if flag.Insecure {
|
||||||
httpClient := &http.Client{
|
httpClient.Transport.(*http.Transport).TLSClientConfig = &tls.Config{
|
||||||
Transport: &http.Transport{
|
|
||||||
TLSClientConfig: &tls.Config{
|
|
||||||
InsecureSkipVerify: true,
|
InsecureSkipVerify: true,
|
||||||
},
|
}
|
||||||
},
|
|
||||||
}
|
}
|
||||||
opts = append(opts, gitea.SetHTTPClient(httpClient))
|
opts = append(opts, gitea.SetHTTPClient(httpClient))
|
||||||
}
|
|
||||||
if flag.Debug {
|
if flag.Debug {
|
||||||
opts = append(opts, gitea.SetDebugMode())
|
opts = append(opts, gitea.SetDebugMode())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -19,7 +19,10 @@ var (
|
|||||||
|
|
||||||
func Default() *zap.Logger {
|
func Default() *zap.Logger {
|
||||||
defaultLoggerOnce.Do(func() {
|
defaultLoggerOnce.Do(func() {
|
||||||
if defaultLogger == nil {
|
if defaultLogger != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
ec := zap.NewProductionEncoderConfig()
|
ec := zap.NewProductionEncoderConfig()
|
||||||
ec.EncodeTime = zapcore.TimeEncoderOfLayout(time.DateTime)
|
ec.EncodeTime = zapcore.TimeEncoderOfLayout(time.DateTime)
|
||||||
ec.EncodeLevel = zapcore.CapitalLevelEncoder
|
ec.EncodeLevel = zapcore.CapitalLevelEncoder
|
||||||
@@ -32,8 +35,14 @@ func Default() *zap.Logger {
|
|||||||
home = os.TempDir()
|
home = os.TempDir()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
logDir := fmt.Sprintf("%s/.gitea-mcp", home)
|
||||||
|
if err := os.MkdirAll(logDir, 0o700); err != nil {
|
||||||
|
// Fallback to temp directory if creation fails
|
||||||
|
logDir = os.TempDir()
|
||||||
|
}
|
||||||
|
|
||||||
wss = append(wss, zapcore.AddSync(&lumberjack.Logger{
|
wss = append(wss, zapcore.AddSync(&lumberjack.Logger{
|
||||||
Filename: fmt.Sprintf("%s/.gitea-mcp/gitea-mcp.log", home),
|
Filename: fmt.Sprintf("%s/gitea-mcp.log", logDir),
|
||||||
MaxSize: 100,
|
MaxSize: 100,
|
||||||
MaxBackups: 10,
|
MaxBackups: 10,
|
||||||
MaxAge: 30,
|
MaxAge: 30,
|
||||||
@@ -59,7 +68,6 @@ func Default() *zap.Logger {
|
|||||||
zap.AddCallerSkip(1),
|
zap.AddCallerSkip(1),
|
||||||
}
|
}
|
||||||
defaultLogger = zap.New(core, options...)
|
defaultLogger = zap.New(core, options...)
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
return defaultLogger
|
return defaultLogger
|
||||||
@@ -71,8 +79,22 @@ func SetDefault(logger *zap.Logger) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func Logger() *zap.Logger {
|
func New() *Logger {
|
||||||
return defaultLogger
|
return &Logger{
|
||||||
|
defaultLogger: Default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type Logger struct {
|
||||||
|
defaultLogger *zap.Logger
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Infof(msg string, args ...any) {
|
||||||
|
l.defaultLogger.Sugar().Infof(msg, args...)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (l *Logger) Errorf(msg string, args ...any) {
|
||||||
|
l.defaultLogger.Sugar().Errorf(msg, args...)
|
||||||
}
|
}
|
||||||
|
|
||||||
func Debug(msg string, fields ...zap.Field) {
|
func Debug(msg string, fields ...zap.Field) {
|
||||||
|
|||||||
Reference in New Issue
Block a user