feat: expand MCP tool coverage from 93 to 299 tools
Some checks failed
release-nightly / release-image (push) Failing after 2m17s

Fork the official gitea-mcp and massively extend API coverage:

- Organization: orgs, members, teams, hooks, blocks, activity (34 tools)
- User: profile, followers, keys, emails, repos, blocks (30 tools)
- Repository: collaborators, webhooks, branch/tag protection, deploy keys,
  topics, git refs/trees/notes, commit status, stars/watchers, forks,
  transfers, mirrors, templates (53 tools)
- Issue: reactions, pins, subscriptions, timeline, templates (16 tools)
- Notifications: list, check, read, repo-scoped (7 tools)
- Settings: API, attachment, repo, UI settings (4 tools)
- Packages: list, get, delete, files, latest, link/unlink (7 tools)
- Miscellaneous: server version, gitignore/label/license templates,
  markdown/markup rendering, node info, signing keys (12 tools)
- Admin: user/org/repo CRUD, system webhooks, cron tasks, unadopted
  repos, emails, badges (23 tools)

Module path updated to git.lethalbits.com/lethalbits/gitea-mcp.
This commit is contained in:
Andrew Miller
2026-03-05 11:03:20 -05:00
parent 9ce5604e4c
commit df25d328d1
66 changed files with 6976 additions and 122 deletions

View File

@@ -41,7 +41,7 @@ This method uses `go run` and requires [Go](https://go.dev) to be installed.
claude mcp add --transport stdio --scope user gitea \ claude mcp add --transport stdio --scope user gitea \
--env GITEA_ACCESS_TOKEN=token \ --env GITEA_ACCESS_TOKEN=token \
--env GITEA_HOST=https://gitea.com \ --env GITEA_HOST=https://gitea.com \
-- go run gitea.com/gitea/gitea-mcp@latest -t stdio -- go run git.lethalbits.com/lethalbits/gitea-mcp@latest -t stdio
``` ```
### Usage with VS Code ### Usage with VS Code
@@ -87,14 +87,14 @@ Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace
### 📥 Download the official binary release ### 📥 Download the official binary release
You can download the official release from [official Gitea MCP binary releases](https://gitea.com/gitea/gitea-mcp/releases). You can download the official release from [official Gitea MCP binary releases](https://git.lethalbits.com/lethalbits/gitea-mcp/releases).
### 🔧 Build from Source ### 🔧 Build from Source
You can download the source code by cloning the repository using Git: You can download the source code by cloning the repository using Git:
```bash ```bash
git clone https://gitea.com/gitea/gitea-mcp.git git clone https://git.lethalbits.com/lethalbits/gitea-mcp.git
``` ```
Before building, make sure you have the following installed: Before building, make sure you have the following installed:

View File

@@ -41,7 +41,7 @@ Model Context Protocol (MCP) 是一种协议,允许通过聊天界面整合各
claude mcp add --transport stdio --scope user gitea \ claude mcp add --transport stdio --scope user gitea \
--env GITEA_ACCESS_TOKEN=token \ --env GITEA_ACCESS_TOKEN=token \
--env GITEA_HOST=https://gitea.com \ --env GITEA_HOST=https://gitea.com \
-- go run gitea.com/gitea/gitea-mcp@latest -t stdio -- go run git.lethalbits.com/lethalbits/gitea-mcp@latest -t stdio
``` ```
### 在 VS Code 中使用 ### 在 VS Code 中使用
@@ -87,14 +87,14 @@ claude mcp add --transport stdio --scope user gitea \
### 📥 下载官方二进制版本 ### 📥 下载官方二进制版本
可在 [官方 Gitea MCP 二进制版本](https://gitea.com/gitea/gitea-mcp/releases) 下载。 可在 [官方 Gitea MCP 二进制版本](https://git.lethalbits.com/lethalbits/gitea-mcp/releases) 下载。
### 🔧 从源码构建 ### 🔧 从源码构建
可用 Git 下载源码: 可用 Git 下载源码:
```bash ```bash
git clone https://gitea.com/gitea/gitea-mcp.git git clone https://git.lethalbits.com/lethalbits/gitea-mcp.git
``` ```
构建前请先安装: 构建前请先安装:

View File

@@ -41,7 +41,7 @@ Model Context Protocol (MCP) 是一種協議,允許透過聊天介面整合各
claude mcp add --transport stdio --scope user gitea \ claude mcp add --transport stdio --scope user gitea \
--env GITEA_ACCESS_TOKEN=token \ --env GITEA_ACCESS_TOKEN=token \
--env GITEA_HOST=https://gitea.com \ --env GITEA_HOST=https://gitea.com \
-- go run gitea.com/gitea/gitea-mcp@latest -t stdio -- go run git.lethalbits.com/lethalbits/gitea-mcp@latest -t stdio
``` ```
### 在 VS Code 中使用 ### 在 VS Code 中使用
@@ -87,14 +87,14 @@ claude mcp add --transport stdio --scope user gitea \
### 📥 下載官方二進位版本 ### 📥 下載官方二進位版本
可至 [官方 Gitea MCP 二進位版本](https://gitea.com/gitea/gitea-mcp/releases) 下載。 可至 [官方 Gitea MCP 二進位版本](https://git.lethalbits.com/lethalbits/gitea-mcp/releases) 下載。
### 🔧 從原始碼建置 ### 🔧 從原始碼建置
可用 Git 下載原始碼: 可用 Git 下載原始碼:
```bash ```bash
git clone https://gitea.com/gitea/gitea-mcp.git git clone https://git.lethalbits.com/lethalbits/gitea-mcp.git
``` ```
建置前請先安裝: 建置前請先安裝:

View File

@@ -7,9 +7,9 @@ import (
"os" "os"
"text/tabwriter" "text/tabwriter"
"gitea.com/gitea/gitea-mcp/operation" "git.lethalbits.com/lethalbits/gitea-mcp/operation"
flagPkg "gitea.com/gitea/gitea-mcp/pkg/flag" flagPkg "git.lethalbits.com/lethalbits/gitea-mcp/pkg/flag"
"gitea.com/gitea/gitea-mcp/pkg/log" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
) )
var ( var (

2
go.mod
View File

@@ -1,4 +1,4 @@
module gitea.com/gitea/gitea-mcp module git.lethalbits.com/lethalbits/gitea-mcp
go 1.26.0 go 1.26.0

View File

@@ -1,8 +1,8 @@
package main package main
import ( import (
"gitea.com/gitea/gitea-mcp/cmd" "git.lethalbits.com/lethalbits/gitea-mcp/cmd"
"gitea.com/gitea/gitea-mcp/pkg/flag" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/flag"
) )
var Version = "dev" var Version = "dev"

View File

@@ -1,7 +1,7 @@
package actions package actions
import ( import (
"gitea.com/gitea/gitea-mcp/pkg/tool" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/tool"
) )
// Tool is the registry for all Actions-related MCP tools. // Tool is the registry for all Actions-related MCP tools.

View File

@@ -9,10 +9,10 @@ import (
"os" "os"
"path/filepath" "path/filepath"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"

View File

@@ -9,10 +9,10 @@ import (
"net/url" "net/url"
"strconv" "strconv"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"

View File

@@ -7,10 +7,10 @@ import (
"net/url" "net/url"
"time" "time"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"

View File

@@ -7,10 +7,10 @@ import (
"net/url" "net/url"
"strconv" "strconv"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"

805
operation/admin/admin.go Normal file
View File

@@ -0,0 +1,805 @@
package admin
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
"git.lethalbits.com/lethalbits/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 (
AdminListUsersToolName = "admin_list_users"
AdminCreateUserToolName = "admin_create_user"
AdminEditUserToolName = "admin_edit_user"
AdminDeleteUserToolName = "admin_delete_user"
AdminRenameUserToolName = "admin_rename_user"
AdminListOrgsToolName = "admin_list_orgs"
AdminCreateOrgToolName = "admin_create_org"
AdminCreateRepoToolName = "admin_create_repo"
AdminListHooksToolName = "admin_list_hooks"
AdminGetHookToolName = "admin_get_hook"
AdminCreateHookToolName = "admin_create_hook"
AdminEditHookToolName = "admin_edit_hook"
AdminDeleteHookToolName = "admin_delete_hook"
ListCronTasksToolName = "admin_list_cron_tasks"
RunCronTaskToolName = "admin_run_cron_task"
ListUnadoptedReposToolName = "admin_list_unadopted_repos"
AdoptUnadoptedRepoToolName = "admin_adopt_repo"
DeleteUnadoptedRepoToolName = "admin_delete_unadopted_repo"
AdminListEmailsToolName = "admin_list_emails"
AdminSearchEmailsToolName = "admin_search_emails"
ListUserBadgesToolName = "admin_list_user_badges"
AddUserBadgesToolName = "admin_add_user_badges"
DeleteUserBadgeToolName = "admin_delete_user_badge"
)
var (
AdminListUsersTool = mcp.NewTool(
AdminListUsersToolName,
mcp.WithDescription("Admin: List all users"),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
AdminCreateUserTool = mcp.NewTool(
AdminCreateUserToolName,
mcp.WithDescription("Admin: Create a new user"),
mcp.WithString("username", mcp.Required(), mcp.Description("username")),
mcp.WithString("email", mcp.Required(), mcp.Description("email address")),
mcp.WithString("password", mcp.Required(), mcp.Description("password")),
mcp.WithString("full_name", mcp.Description("full name")),
mcp.WithBoolean("must_change_password", mcp.Description("require password change on first login (default: true)")),
mcp.WithBoolean("send_notify", mcp.Description("send notification email")),
)
AdminEditUserTool = mcp.NewTool(
AdminEditUserToolName,
mcp.WithDescription("Admin: Edit a user account"),
mcp.WithString("username", mcp.Required(), mcp.Description("username to edit")),
mcp.WithString("email", mcp.Description("new email")),
mcp.WithString("password", mcp.Description("new password")),
mcp.WithString("full_name", mcp.Description("new full name")),
mcp.WithBoolean("must_change_password", mcp.Description("require password change")),
mcp.WithBoolean("admin", mcp.Description("set admin status")),
mcp.WithBoolean("active", mcp.Description("set active status")),
mcp.WithBoolean("prohibit_login", mcp.Description("prohibit login")),
mcp.WithBoolean("restricted", mcp.Description("set restricted status")),
mcp.WithBoolean("allow_create_organization", mcp.Description("allow creating organizations")),
)
AdminDeleteUserTool = mcp.NewTool(
AdminDeleteUserToolName,
mcp.WithDescription("Admin: Delete a user account"),
mcp.WithString("username", mcp.Required(), mcp.Description("username to delete")),
)
AdminRenameUserTool = mcp.NewTool(
AdminRenameUserToolName,
mcp.WithDescription("Admin: Rename a user"),
mcp.WithString("username", mcp.Required(), mcp.Description("current username")),
mcp.WithString("new_username", mcp.Required(), mcp.Description("new username")),
)
AdminListOrgsTool = mcp.NewTool(
AdminListOrgsToolName,
mcp.WithDescription("Admin: List all organizations"),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
AdminCreateOrgTool = mcp.NewTool(
AdminCreateOrgToolName,
mcp.WithDescription("Admin: Create an organization for a user"),
mcp.WithString("username", mcp.Required(), mcp.Description("owner username")),
mcp.WithString("name", mcp.Required(), mcp.Description("organization name")),
mcp.WithString("full_name", mcp.Description("organization full name")),
mcp.WithString("description", mcp.Description("organization description")),
mcp.WithString("visibility", mcp.Description("visibility: public, limited, private (default: public)")),
)
AdminCreateRepoTool = mcp.NewTool(
AdminCreateRepoToolName,
mcp.WithDescription("Admin: Create a repository for a user"),
mcp.WithString("username", mcp.Required(), mcp.Description("owner username")),
mcp.WithString("name", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("description", mcp.Description("repository description")),
mcp.WithBoolean("private", mcp.Description("make repository private")),
mcp.WithBoolean("auto_init", mcp.Description("auto-initialize with README")),
)
AdminListHooksTool = mcp.NewTool(
AdminListHooksToolName,
mcp.WithDescription("Admin: List system webhooks"),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
AdminGetHookTool = mcp.NewTool(
AdminGetHookToolName,
mcp.WithDescription("Admin: Get a system webhook by ID"),
mcp.WithNumber("id", mcp.Required(), mcp.Description("webhook ID")),
)
AdminCreateHookTool = mcp.NewTool(
AdminCreateHookToolName,
mcp.WithDescription("Admin: Create a system webhook"),
mcp.WithString("type", mcp.Required(), mcp.Description("hook type: gitea, gogs, slack, discord, dingtalk, telegram, msteams, feishu, wechatwork, packagist")),
mcp.WithString("url", mcp.Required(), mcp.Description("target URL")),
mcp.WithString("content_type", mcp.Description("content type: json or form (default: json)")),
mcp.WithString("secret", mcp.Description("webhook secret")),
mcp.WithBoolean("active", mcp.Description("whether the webhook is active (default: true)")),
)
AdminEditHookTool = mcp.NewTool(
AdminEditHookToolName,
mcp.WithDescription("Admin: Edit a system webhook"),
mcp.WithNumber("id", mcp.Required(), mcp.Description("webhook ID")),
mcp.WithString("url", mcp.Description("target URL")),
mcp.WithString("content_type", mcp.Description("content type: json or form")),
mcp.WithString("secret", mcp.Description("webhook secret")),
mcp.WithBoolean("active", mcp.Description("whether the webhook is active")),
)
AdminDeleteHookTool = mcp.NewTool(
AdminDeleteHookToolName,
mcp.WithDescription("Admin: Delete a system webhook"),
mcp.WithNumber("id", mcp.Required(), mcp.Description("webhook ID")),
)
ListCronTasksTool = mcp.NewTool(
ListCronTasksToolName,
mcp.WithDescription("Admin: List cron tasks"),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
RunCronTaskTool = mcp.NewTool(
RunCronTaskToolName,
mcp.WithDescription("Admin: Run a cron task manually"),
mcp.WithString("task", mcp.Required(), mcp.Description("cron task name")),
)
ListUnadoptedReposTool = mcp.NewTool(
ListUnadoptedReposToolName,
mcp.WithDescription("Admin: List unadopted repositories"),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
AdoptUnadoptedRepoTool = mcp.NewTool(
AdoptUnadoptedRepoToolName,
mcp.WithDescription("Admin: Adopt an unadopted repository"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
)
DeleteUnadoptedRepoTool = mcp.NewTool(
DeleteUnadoptedRepoToolName,
mcp.WithDescription("Admin: Delete an unadopted repository"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
)
AdminListEmailsTool = mcp.NewTool(
AdminListEmailsToolName,
mcp.WithDescription("Admin: List all emails"),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
AdminSearchEmailsTool = mcp.NewTool(
AdminSearchEmailsToolName,
mcp.WithDescription("Admin: Search emails"),
mcp.WithString("query", mcp.Required(), mcp.Description("search query")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
ListUserBadgesTool = mcp.NewTool(
ListUserBadgesToolName,
mcp.WithDescription("Admin: List badges for a user"),
mcp.WithString("username", mcp.Required(), mcp.Description("username")),
)
AddUserBadgesTool = mcp.NewTool(
AddUserBadgesToolName,
mcp.WithDescription("Admin: Add badges to a user"),
mcp.WithString("username", mcp.Required(), mcp.Description("username")),
mcp.WithString("slugs", mcp.Required(), mcp.Description("comma-separated badge slugs to add")),
)
DeleteUserBadgeTool = mcp.NewTool(
DeleteUserBadgeToolName,
mcp.WithDescription("Admin: Remove badges from a user"),
mcp.WithString("username", mcp.Required(), mcp.Description("username")),
mcp.WithString("slugs", mcp.Required(), mcp.Description("comma-separated badge slugs to remove")),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: AdminListUsersTool, Handler: AdminListUsersFn})
Tool.RegisterRead(server.ServerTool{Tool: AdminListOrgsTool, Handler: AdminListOrgsFn})
Tool.RegisterRead(server.ServerTool{Tool: AdminListHooksTool, Handler: AdminListHooksFn})
Tool.RegisterRead(server.ServerTool{Tool: AdminGetHookTool, Handler: AdminGetHookFn})
Tool.RegisterRead(server.ServerTool{Tool: ListCronTasksTool, Handler: ListCronTasksFn})
Tool.RegisterRead(server.ServerTool{Tool: ListUnadoptedReposTool, Handler: ListUnadoptedReposFn})
Tool.RegisterRead(server.ServerTool{Tool: AdminListEmailsTool, Handler: AdminListEmailsFn})
Tool.RegisterRead(server.ServerTool{Tool: AdminSearchEmailsTool, Handler: AdminSearchEmailsFn})
Tool.RegisterRead(server.ServerTool{Tool: ListUserBadgesTool, Handler: ListUserBadgesFn})
Tool.RegisterWrite(server.ServerTool{Tool: AdminCreateUserTool, Handler: AdminCreateUserFn})
Tool.RegisterWrite(server.ServerTool{Tool: AdminEditUserTool, Handler: AdminEditUserFn})
Tool.RegisterWrite(server.ServerTool{Tool: AdminDeleteUserTool, Handler: AdminDeleteUserFn})
Tool.RegisterWrite(server.ServerTool{Tool: AdminRenameUserTool, Handler: AdminRenameUserFn})
Tool.RegisterWrite(server.ServerTool{Tool: AdminCreateOrgTool, Handler: AdminCreateOrgFn})
Tool.RegisterWrite(server.ServerTool{Tool: AdminCreateRepoTool, Handler: AdminCreateRepoFn})
Tool.RegisterWrite(server.ServerTool{Tool: AdminCreateHookTool, Handler: AdminCreateHookFn})
Tool.RegisterWrite(server.ServerTool{Tool: AdminEditHookTool, Handler: AdminEditHookFn})
Tool.RegisterWrite(server.ServerTool{Tool: AdminDeleteHookTool, Handler: AdminDeleteHookFn})
Tool.RegisterWrite(server.ServerTool{Tool: RunCronTaskTool, Handler: RunCronTaskFn})
Tool.RegisterWrite(server.ServerTool{Tool: AdoptUnadoptedRepoTool, Handler: AdoptUnadoptedRepoFn})
Tool.RegisterWrite(server.ServerTool{Tool: DeleteUnadoptedRepoTool, Handler: DeleteUnadoptedRepoFn})
Tool.RegisterWrite(server.ServerTool{Tool: AddUserBadgesTool, Handler: AddUserBadgesFn})
Tool.RegisterWrite(server.ServerTool{Tool: DeleteUserBadgeTool, Handler: DeleteUserBadgeFn})
}
func AdminListUsersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called AdminListUsersFn")
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
users, _, err := client.AdminListUsers(gitea_sdk.AdminListUsersOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("admin list users err: %v", err))
}
return to.TextResult(users)
}
func AdminCreateUserFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called AdminCreateUserFn")
username, _ := req.GetArguments()["username"].(string)
email, _ := req.GetArguments()["email"].(string)
password, _ := req.GetArguments()["password"].(string)
if username == "" || email == "" || password == "" {
return to.ErrorResult(errors.New("username, email, and password are required"))
}
mustChange := true
if v, ok := req.GetArguments()["must_change_password"].(bool); ok {
mustChange = v
}
opt := gitea_sdk.CreateUserOption{
LoginName: username,
Username: username,
Email: email,
Password: password,
MustChangePassword: &mustChange,
}
if v, ok := req.GetArguments()["full_name"].(string); ok {
opt.FullName = v
}
if v, ok := req.GetArguments()["send_notify"].(bool); ok {
opt.SendNotify = v
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
user, _, err := client.AdminCreateUser(opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("admin create user err: %v", err))
}
return to.TextResult(user)
}
func AdminEditUserFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called AdminEditUserFn")
username, _ := req.GetArguments()["username"].(string)
if username == "" {
return to.ErrorResult(errors.New("username is required"))
}
opt := gitea_sdk.EditUserOption{}
if v, ok := req.GetArguments()["email"].(string); ok {
opt.Email = &v
}
if v, ok := req.GetArguments()["password"].(string); ok {
opt.Password = v
}
if v, ok := req.GetArguments()["full_name"].(string); ok {
opt.FullName = &v
}
if v, ok := req.GetArguments()["must_change_password"].(bool); ok {
opt.MustChangePassword = &v
}
if v, ok := req.GetArguments()["admin"].(bool); ok {
opt.Admin = &v
}
if v, ok := req.GetArguments()["active"].(bool); ok {
opt.Active = &v
}
if v, ok := req.GetArguments()["prohibit_login"].(bool); ok {
opt.ProhibitLogin = &v
}
if v, ok := req.GetArguments()["restricted"].(bool); ok {
opt.Restricted = &v
}
if v, ok := req.GetArguments()["allow_create_organization"].(bool); ok {
opt.AllowCreateOrganization = &v
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.AdminEditUser(username, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("admin edit user err: %v", err))
}
return to.TextResult(map[string]string{"status": "updated", "username": username})
}
func AdminDeleteUserFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called AdminDeleteUserFn")
username, _ := req.GetArguments()["username"].(string)
if username == "" {
return to.ErrorResult(errors.New("username is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.AdminDeleteUser(username)
if err != nil {
return to.ErrorResult(fmt.Errorf("admin delete user err: %v", err))
}
return to.TextResult(map[string]string{"status": "deleted", "username": username})
}
func AdminRenameUserFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called AdminRenameUserFn")
username, _ := req.GetArguments()["username"].(string)
newUsername, _ := req.GetArguments()["new_username"].(string)
if username == "" || newUsername == "" {
return to.ErrorResult(errors.New("username and new_username are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.AdminRenameUser(username, gitea_sdk.RenameUserOption{NewUsername: newUsername})
if err != nil {
return to.ErrorResult(fmt.Errorf("admin rename user err: %v", err))
}
return to.TextResult(map[string]string{"status": "renamed", "old": username, "new": newUsername})
}
func AdminListOrgsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called AdminListOrgsFn")
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
orgs, _, err := client.AdminListOrgs(gitea_sdk.AdminListOrgsOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("admin list orgs err: %v", err))
}
return to.TextResult(orgs)
}
func AdminCreateOrgFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called AdminCreateOrgFn")
username, _ := req.GetArguments()["username"].(string)
name, _ := req.GetArguments()["name"].(string)
if username == "" || name == "" {
return to.ErrorResult(errors.New("username and name are required"))
}
opt := gitea_sdk.CreateOrgOption{
Name: name,
}
if v, ok := req.GetArguments()["full_name"].(string); ok {
opt.FullName = v
}
if v, ok := req.GetArguments()["description"].(string); ok {
opt.Description = v
}
if v, ok := req.GetArguments()["visibility"].(string); ok {
opt.Visibility = gitea_sdk.VisibleType(v)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
org, _, err := client.AdminCreateOrg(username, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("admin create org err: %v", err))
}
return to.TextResult(org)
}
func AdminCreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called AdminCreateRepoFn")
username, _ := req.GetArguments()["username"].(string)
name, _ := req.GetArguments()["name"].(string)
if username == "" || name == "" {
return to.ErrorResult(errors.New("username and name are required"))
}
opt := gitea_sdk.CreateRepoOption{
Name: name,
}
if v, ok := req.GetArguments()["description"].(string); ok {
opt.Description = v
}
if v, ok := req.GetArguments()["private"].(bool); ok {
opt.Private = v
}
if v, ok := req.GetArguments()["auto_init"].(bool); ok {
opt.AutoInit = v
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
repo, _, err := client.AdminCreateRepo(username, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("admin create repo err: %v", err))
}
return to.TextResult(repo)
}
func AdminListHooksFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called AdminListHooksFn")
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
hooks, _, err := client.ListAdminHooks(gitea_sdk.ListAdminHooksOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("admin list hooks err: %v", err))
}
return to.TextResult(hooks)
}
func AdminGetHookFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called AdminGetHookFn")
id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
hook, _, err := client.GetAdminHook(id)
if err != nil {
return to.ErrorResult(fmt.Errorf("admin get hook err: %v", err))
}
return to.TextResult(hook)
}
func AdminCreateHookFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called AdminCreateHookFn")
hookType, _ := req.GetArguments()["type"].(string)
url, _ := req.GetArguments()["url"].(string)
if hookType == "" || url == "" {
return to.ErrorResult(errors.New("type and url are required"))
}
contentType := "json"
if v, ok := req.GetArguments()["content_type"].(string); ok {
contentType = v
}
config := map[string]string{
"url": url,
"content_type": contentType,
}
if v, ok := req.GetArguments()["secret"].(string); ok {
config["secret"] = v
}
opt := gitea_sdk.CreateHookOption{
Type: gitea_sdk.HookType(hookType),
Config: config,
Active: true,
}
if v, ok := req.GetArguments()["active"].(bool); ok {
opt.Active = v
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
hook, _, err := client.CreateAdminHook(opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("admin create hook err: %v", err))
}
return to.TextResult(hook)
}
func AdminEditHookFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called AdminEditHookFn")
id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil {
return to.ErrorResult(err)
}
opt := gitea_sdk.EditHookOption{}
config := map[string]string{}
if v, ok := req.GetArguments()["url"].(string); ok {
config["url"] = v
}
if v, ok := req.GetArguments()["content_type"].(string); ok {
config["content_type"] = v
}
if v, ok := req.GetArguments()["secret"].(string); ok {
config["secret"] = v
}
if len(config) > 0 {
opt.Config = config
}
if v, ok := req.GetArguments()["active"].(bool); ok {
opt.Active = &v
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, _, err = client.EditAdminHook(id, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("admin edit hook err: %v", err))
}
return to.TextResult(map[string]string{"status": "updated"})
}
func AdminDeleteHookFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called AdminDeleteHookFn")
id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.DeleteAdminHook(id)
if err != nil {
return to.ErrorResult(fmt.Errorf("admin delete hook err: %v", err))
}
return to.TextResult(map[string]string{"status": "deleted"})
}
func ListCronTasksFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListCronTasksFn")
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
tasks, _, err := client.ListCronTasks(gitea_sdk.ListCronTaskOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list cron tasks err: %v", err))
}
return to.TextResult(tasks)
}
func RunCronTaskFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called RunCronTaskFn")
task, _ := req.GetArguments()["task"].(string)
if task == "" {
return to.ErrorResult(errors.New("task is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.RunCronTasks(task)
if err != nil {
return to.ErrorResult(fmt.Errorf("run cron task err: %v", err))
}
return to.TextResult(map[string]string{"status": "triggered", "task": task})
}
func ListUnadoptedReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListUnadoptedReposFn")
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
repos, _, err := client.ListUnadoptedRepos(gitea_sdk.ListUnadoptedReposOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list unadopted repos err: %v", err))
}
return to.TextResult(repos)
}
func AdoptUnadoptedRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called AdoptUnadoptedRepoFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.AdoptUnadoptedRepo(owner, repo)
if err != nil {
return to.ErrorResult(fmt.Errorf("adopt repo err: %v", err))
}
return to.TextResult(map[string]string{"status": "adopted"})
}
func DeleteUnadoptedRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteUnadoptedRepoFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.DeleteUnadoptedRepo(owner, repo)
if err != nil {
return to.ErrorResult(fmt.Errorf("delete unadopted repo err: %v", err))
}
return to.TextResult(map[string]string{"status": "deleted"})
}
func AdminListEmailsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called AdminListEmailsFn")
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
emails, _, err := client.ListAdminEmails(gitea_sdk.ListAdminEmailsOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("admin list emails err: %v", err))
}
return to.TextResult(emails)
}
func AdminSearchEmailsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called AdminSearchEmailsFn")
query, _ := req.GetArguments()["query"].(string)
if query == "" {
return to.ErrorResult(errors.New("query is required"))
}
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
emails, _, err := client.SearchAdminEmails(gitea_sdk.SearchAdminEmailsOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
Query: query,
})
if err != nil {
return to.ErrorResult(fmt.Errorf("admin search emails err: %v", err))
}
return to.TextResult(emails)
}
func splitAndTrim(s string) []string {
var result []string
for _, part := range splitByComma(s) {
trimmed := trimSpace(part)
if trimmed != "" {
result = append(result, trimmed)
}
}
return result
}
func splitByComma(s string) []string {
var result []string
start := 0
for i := 0; i < len(s); i++ {
if s[i] == ',' {
result = append(result, s[start:i])
start = i + 1
}
}
result = append(result, s[start:])
return result
}
func trimSpace(s string) string {
start, end := 0, len(s)
for start < end && (s[start] == ' ' || s[start] == '\t') {
start++
}
for end > start && (s[end-1] == ' ' || s[end-1] == '\t') {
end--
}
return s[start:end]
}
func ListUserBadgesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListUserBadgesFn")
username, _ := req.GetArguments()["username"].(string)
if username == "" {
return to.ErrorResult(errors.New("username is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
badges, _, err := client.ListUserBadges(username)
if err != nil {
return to.ErrorResult(fmt.Errorf("list user badges err: %v", err))
}
return to.TextResult(badges)
}
func AddUserBadgesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called AddUserBadgesFn")
username, _ := req.GetArguments()["username"].(string)
slugs, _ := req.GetArguments()["slugs"].(string)
if username == "" || slugs == "" {
return to.ErrorResult(errors.New("username and slugs are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.AddUserBadges(username, gitea_sdk.UserBadgeOption{
BadgeSlugs: splitAndTrim(slugs),
})
if err != nil {
return to.ErrorResult(fmt.Errorf("add user badges err: %v", err))
}
return to.TextResult(map[string]string{"status": "added"})
}
func DeleteUserBadgeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteUserBadgeFn")
username, _ := req.GetArguments()["username"].(string)
slugs, _ := req.GetArguments()["slugs"].(string)
if username == "" || slugs == "" {
return to.ErrorResult(errors.New("username and slugs are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.DeleteUserBadge(username, gitea_sdk.UserBadgeOption{
BadgeSlugs: splitAndTrim(slugs),
})
if err != nil {
return to.ErrorResult(fmt.Errorf("delete user badge err: %v", err))
}
return to.TextResult(map[string]string{"status": "removed"})
}

View File

@@ -5,11 +5,11 @@ import (
"errors" "errors"
"fmt" "fmt"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
"gitea.com/gitea/gitea-mcp/pkg/tool" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/tool"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"

151
operation/issue/pin.go Normal file
View File

@@ -0,0 +1,151 @@
package issue
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
ListPinnedIssuesToolName = "list_pinned_issues"
PinIssueToolName = "pin_issue"
UnpinIssueToolName = "unpin_issue"
MoveIssuePinToolName = "move_issue_pin"
)
var (
ListPinnedIssuesTool = mcp.NewTool(
ListPinnedIssuesToolName,
mcp.WithDescription("List pinned issues in a repository"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
)
PinIssueTool = mcp.NewTool(
PinIssueToolName,
mcp.WithDescription("Pin an issue in a repository"),
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")),
)
UnpinIssueTool = mcp.NewTool(
UnpinIssueToolName,
mcp.WithDescription("Unpin an issue from a repository"),
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")),
)
MoveIssuePinTool = mcp.NewTool(
MoveIssuePinToolName,
mcp.WithDescription("Move a pinned issue to a new position"),
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("position", mcp.Required(), mcp.Description("new position (1-based)")),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: ListPinnedIssuesTool, Handler: ListPinnedIssuesFn})
Tool.RegisterWrite(server.ServerTool{Tool: PinIssueTool, Handler: PinIssueFn})
Tool.RegisterWrite(server.ServerTool{Tool: UnpinIssueTool, Handler: UnpinIssueFn})
Tool.RegisterWrite(server.ServerTool{Tool: MoveIssuePinTool, Handler: MoveIssuePinFn})
}
func ListPinnedIssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListPinnedIssuesFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
issues, _, err := client.ListRepoPinnedIssues(owner, repo)
if err != nil {
return to.ErrorResult(fmt.Errorf("list pinned issues err: %v", err))
}
return to.TextResult(issues)
}
func PinIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called PinIssueFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.PinIssue(owner, repo, index)
if err != nil {
return to.ErrorResult(fmt.Errorf("pin issue err: %v", err))
}
return to.TextResult(map[string]string{"status": "pinned"})
}
func UnpinIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called UnpinIssueFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.UnpinIssue(owner, repo, index)
if err != nil {
return to.ErrorResult(fmt.Errorf("unpin issue err: %v", err))
}
return to.TextResult(map[string]string{"status": "unpinned"})
}
func MoveIssuePinFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called MoveIssuePinFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil {
return to.ErrorResult(err)
}
position, ok := req.GetArguments()["position"].(float64)
if !ok {
return to.ErrorResult(errors.New("position is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.MoveIssuePin(owner, repo, index, int64(position))
if err != nil {
return to.ErrorResult(fmt.Errorf("move issue pin err: %v", err))
}
return to.TextResult(map[string]any{"status": "moved", "position": int64(position)})
}

224
operation/issue/reaction.go Normal file
View File

@@ -0,0 +1,224 @@
package issue
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
ListIssueReactionsToolName = "list_issue_reactions"
PostIssueReactionToolName = "post_issue_reaction"
DeleteIssueReactionToolName = "delete_issue_reaction"
GetIssueCommentReactionsToolName = "get_issue_comment_reactions"
PostIssueCommentReactionToolName = "post_issue_comment_reaction"
DeleteIssueCommentReactionToolName = "delete_issue_comment_reaction"
)
var (
ListIssueReactionsTool = mcp.NewTool(
ListIssueReactionsToolName,
mcp.WithDescription("List reactions 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")),
)
PostIssueReactionTool = mcp.NewTool(
PostIssueReactionToolName,
mcp.WithDescription("Add a reaction 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.WithString("reaction", mcp.Required(), mcp.Description("reaction emoji (e.g., +1, -1, laugh, confused, heart, hooray, rocket, eyes)")),
)
DeleteIssueReactionTool = mcp.NewTool(
DeleteIssueReactionToolName,
mcp.WithDescription("Remove a reaction 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.WithString("reaction", mcp.Required(), mcp.Description("reaction emoji to remove")),
)
GetIssueCommentReactionsTool = mcp.NewTool(
GetIssueCommentReactionsToolName,
mcp.WithDescription("List reactions on an issue comment"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithNumber("comment_id", mcp.Required(), mcp.Description("comment ID")),
)
PostIssueCommentReactionTool = mcp.NewTool(
PostIssueCommentReactionToolName,
mcp.WithDescription("Add a reaction to an issue comment"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithNumber("comment_id", mcp.Required(), mcp.Description("comment ID")),
mcp.WithString("reaction", mcp.Required(), mcp.Description("reaction emoji")),
)
DeleteIssueCommentReactionTool = mcp.NewTool(
DeleteIssueCommentReactionToolName,
mcp.WithDescription("Remove a reaction from an issue comment"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithNumber("comment_id", mcp.Required(), mcp.Description("comment ID")),
mcp.WithString("reaction", mcp.Required(), mcp.Description("reaction emoji to remove")),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: ListIssueReactionsTool, Handler: ListIssueReactionsFn})
Tool.RegisterRead(server.ServerTool{Tool: GetIssueCommentReactionsTool, Handler: GetIssueCommentReactionsFn})
Tool.RegisterWrite(server.ServerTool{Tool: PostIssueReactionTool, Handler: PostIssueReactionFn})
Tool.RegisterWrite(server.ServerTool{Tool: DeleteIssueReactionTool, Handler: DeleteIssueReactionFn})
Tool.RegisterWrite(server.ServerTool{Tool: PostIssueCommentReactionTool, Handler: PostIssueCommentReactionFn})
Tool.RegisterWrite(server.ServerTool{Tool: DeleteIssueCommentReactionTool, Handler: DeleteIssueCommentReactionFn})
}
func ListIssueReactionsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListIssueReactionsFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
reactions, _, err := client.ListIssueReactions(owner, repo, index, gitea_sdk.ListIssueReactionsOptions{})
if err != nil {
return to.ErrorResult(fmt.Errorf("list issue reactions err: %v", err))
}
return to.TextResult(reactions)
}
func PostIssueReactionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called PostIssueReactionFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
reaction, _ := req.GetArguments()["reaction"].(string)
if owner == "" || repo == "" || reaction == "" {
return to.ErrorResult(errors.New("owner, repo, and reaction are required"))
}
index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
r, _, err := client.PostIssueReaction(owner, repo, index, reaction)
if err != nil {
return to.ErrorResult(fmt.Errorf("post issue reaction err: %v", err))
}
return to.TextResult(r)
}
func DeleteIssueReactionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteIssueReactionFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
reaction, _ := req.GetArguments()["reaction"].(string)
if owner == "" || repo == "" || reaction == "" {
return to.ErrorResult(errors.New("owner, repo, and reaction are required"))
}
index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.DeleteIssueReaction(owner, repo, index, reaction)
if err != nil {
return to.ErrorResult(fmt.Errorf("delete issue reaction err: %v", err))
}
return to.TextResult(map[string]string{"status": "removed", "reaction": reaction})
}
func GetIssueCommentReactionsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetIssueCommentReactionsFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
commentID, err := params.GetIndex(req.GetArguments(), "comment_id")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
reactions, _, err := client.GetIssueCommentReactions(owner, repo, commentID)
if err != nil {
return to.ErrorResult(fmt.Errorf("get issue comment reactions err: %v", err))
}
return to.TextResult(reactions)
}
func PostIssueCommentReactionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called PostIssueCommentReactionFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
reaction, _ := req.GetArguments()["reaction"].(string)
if owner == "" || repo == "" || reaction == "" {
return to.ErrorResult(errors.New("owner, repo, and reaction are required"))
}
commentID, err := params.GetIndex(req.GetArguments(), "comment_id")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
r, _, err := client.PostIssueCommentReaction(owner, repo, commentID, reaction)
if err != nil {
return to.ErrorResult(fmt.Errorf("post issue comment reaction err: %v", err))
}
return to.TextResult(r)
}
func DeleteIssueCommentReactionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteIssueCommentReactionFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
reaction, _ := req.GetArguments()["reaction"].(string)
if owner == "" || repo == "" || reaction == "" {
return to.ErrorResult(errors.New("owner, repo, and reaction are required"))
}
commentID, err := params.GetIndex(req.GetArguments(), "comment_id")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.DeleteIssueCommentReaction(owner, repo, commentID, reaction)
if err != nil {
return to.ErrorResult(fmt.Errorf("delete issue comment reaction err: %v", err))
}
return to.TextResult(map[string]string{"status": "removed", "reaction": reaction})
}

View File

@@ -0,0 +1,152 @@
package issue
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
ListIssueSubscribersToolName = "list_issue_subscribers"
CheckIssueSubscriptionToolName = "check_issue_subscription"
IssueSubscribeToolName = "issue_subscribe"
IssueUnSubscribeToolName = "issue_unsubscribe"
)
var (
ListIssueSubscribersTool = mcp.NewTool(
ListIssueSubscribersToolName,
mcp.WithDescription("List users subscribed 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")),
)
CheckIssueSubscriptionTool = mcp.NewTool(
CheckIssueSubscriptionToolName,
mcp.WithDescription("Check if the authenticated user is subscribed 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")),
)
IssueSubscribeTool = mcp.NewTool(
IssueSubscribeToolName,
mcp.WithDescription("Subscribe 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")),
)
IssueUnSubscribeTool = mcp.NewTool(
IssueUnSubscribeToolName,
mcp.WithDescription("Unsubscribe 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")),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: ListIssueSubscribersTool, Handler: ListIssueSubscribersFn})
Tool.RegisterRead(server.ServerTool{Tool: CheckIssueSubscriptionTool, Handler: CheckIssueSubscriptionFn})
Tool.RegisterWrite(server.ServerTool{Tool: IssueSubscribeTool, Handler: IssueSubscribeFn})
Tool.RegisterWrite(server.ServerTool{Tool: IssueUnSubscribeTool, Handler: IssueUnSubscribeFn})
}
func ListIssueSubscribersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListIssueSubscribersFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
users, _, err := client.ListIssueSubscribers(owner, repo, index, gitea_sdk.ListIssueSubscribersOptions{})
if err != nil {
return to.ErrorResult(fmt.Errorf("list issue subscribers err: %v", err))
}
return to.TextResult(users)
}
func CheckIssueSubscriptionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CheckIssueSubscriptionFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
info, _, err := client.CheckIssueSubscription(owner, repo, index)
if err != nil {
return to.ErrorResult(fmt.Errorf("check issue subscription err: %v", err))
}
return to.TextResult(info)
}
func IssueSubscribeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called IssueSubscribeFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.IssueSubscribe(owner, repo, index)
if err != nil {
return to.ErrorResult(fmt.Errorf("issue subscribe err: %v", err))
}
return to.TextResult(map[string]string{"status": "subscribed"})
}
func IssueUnSubscribeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called IssueUnSubscribeFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.IssueUnSubscribe(owner, repo, index)
if err != nil {
return to.ErrorResult(fmt.Errorf("issue unsubscribe err: %v", err))
}
return to.TextResult(map[string]string{"status": "unsubscribed"})
}

View File

@@ -0,0 +1,89 @@
package issue
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
ListIssueTimelineToolName = "list_issue_timeline"
GetIssueTemplatesToolName = "get_issue_templates"
)
var (
ListIssueTimelineTool = mcp.NewTool(
ListIssueTimelineToolName,
mcp.WithDescription("List timeline events for an issue (comments, labels, milestones, references, etc.)"),
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("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
GetIssueTemplatesTool = mcp.NewTool(
GetIssueTemplatesToolName,
mcp.WithDescription("Get issue templates for a repository"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: ListIssueTimelineTool, Handler: ListIssueTimelineFn})
Tool.RegisterRead(server.ServerTool{Tool: GetIssueTemplatesTool, Handler: GetIssueTemplatesFn})
}
func ListIssueTimelineFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListIssueTimelineFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
index, err := params.GetIndex(req.GetArguments(), "index")
if err != nil {
return to.ErrorResult(err)
}
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
events, _, err := client.ListIssueTimeline(owner, repo, index, gitea_sdk.ListIssueCommentOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list issue timeline err: %v", err))
}
return to.TextResult(events)
}
func GetIssueTemplatesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetIssueTemplatesFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
templates, _, err := client.GetIssueTemplates(owner, repo)
if err != nil {
return to.ErrorResult(fmt.Errorf("get issue templates err: %v", err))
}
return to.TextResult(templates)
}

View File

@@ -5,11 +5,11 @@ import (
"errors" "errors"
"fmt" "fmt"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
"gitea.com/gitea/gitea-mcp/pkg/tool" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/tool"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"

View File

@@ -5,11 +5,11 @@ import (
"errors" "errors"
"fmt" "fmt"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
"gitea.com/gitea/gitea-mcp/pkg/tool" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/tool"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"

View File

@@ -0,0 +1,325 @@
package miscellaneous
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
"git.lethalbits.com/lethalbits/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 (
GetServerVersionToolName = "get_server_version"
ListGitignoreTemplatesToolName = "list_gitignore_templates"
GetGitignoreTemplateToolName = "get_gitignore_template"
ListLabelTemplatesToolName = "list_label_templates"
GetLabelTemplateToolName = "get_label_template"
ListLicenseTemplatesToolName = "list_license_templates"
GetLicenseTemplateToolName = "get_license_template"
RenderMarkdownToolName = "render_markdown"
RenderMarkupToolName = "render_markup"
GetNodeInfoToolName = "get_node_info"
GetSigningKeyGPGToolName = "get_signing_key_gpg"
GetSigningKeySSHToolName = "get_signing_key_ssh"
)
var (
GetServerVersionTool = mcp.NewTool(
GetServerVersionToolName,
mcp.WithDescription("Get the Gitea server version"),
)
ListGitignoreTemplatesTool = mcp.NewTool(
ListGitignoreTemplatesToolName,
mcp.WithDescription("List available .gitignore templates"),
)
GetGitignoreTemplateTool = mcp.NewTool(
GetGitignoreTemplateToolName,
mcp.WithDescription("Get the content of a .gitignore template by name"),
mcp.WithString("name", mcp.Required(), mcp.Description("template name (e.g., Go, Python, Node)")),
)
ListLabelTemplatesTool = mcp.NewTool(
ListLabelTemplatesToolName,
mcp.WithDescription("List available label templates"),
)
GetLabelTemplateTool = mcp.NewTool(
GetLabelTemplateToolName,
mcp.WithDescription("Get the labels from a label template"),
mcp.WithString("name", mcp.Required(), mcp.Description("template name")),
)
ListLicenseTemplatesTool = mcp.NewTool(
ListLicenseTemplatesToolName,
mcp.WithDescription("List available license templates"),
)
GetLicenseTemplateTool = mcp.NewTool(
GetLicenseTemplateToolName,
mcp.WithDescription("Get the content of a license template"),
mcp.WithString("name", mcp.Required(), mcp.Description("license name (e.g., MIT, GPL-3.0, Apache-2.0)")),
)
RenderMarkdownTool = mcp.NewTool(
RenderMarkdownToolName,
mcp.WithDescription("Render markdown text to HTML"),
mcp.WithString("text", mcp.Required(), mcp.Description("markdown text to render")),
mcp.WithString("mode", mcp.Description("render mode: markdown, gfm, comment (default: markdown)")),
mcp.WithString("context", mcp.Description("context for relative links (owner/repo format)")),
mcp.WithBoolean("wiki", mcp.Description("treat text as wiki content")),
)
RenderMarkupTool = mcp.NewTool(
RenderMarkupToolName,
mcp.WithDescription("Render markup content to HTML (supports multiple markup languages)"),
mcp.WithString("text", mcp.Required(), mcp.Description("markup text to render")),
mcp.WithString("mode", mcp.Description("render mode")),
mcp.WithString("context", mcp.Description("context for relative links (owner/repo format)")),
mcp.WithString("filepath", mcp.Description("file path to determine markup language")),
mcp.WithBoolean("wiki", mcp.Description("treat text as wiki content")),
)
GetNodeInfoTool = mcp.NewTool(
GetNodeInfoToolName,
mcp.WithDescription("Get the NodeInfo of the Gitea instance (federation metadata)"),
)
GetSigningKeyGPGTool = mcp.NewTool(
GetSigningKeyGPGToolName,
mcp.WithDescription("Get the GPG signing key of the Gitea instance"),
)
GetSigningKeySSHTool = mcp.NewTool(
GetSigningKeySSHToolName,
mcp.WithDescription("Get the SSH signing key of the Gitea instance"),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: GetServerVersionTool, Handler: GetServerVersionFn})
Tool.RegisterRead(server.ServerTool{Tool: ListGitignoreTemplatesTool, Handler: ListGitignoreTemplatesFn})
Tool.RegisterRead(server.ServerTool{Tool: GetGitignoreTemplateTool, Handler: GetGitignoreTemplateFn})
Tool.RegisterRead(server.ServerTool{Tool: ListLabelTemplatesTool, Handler: ListLabelTemplatesFn})
Tool.RegisterRead(server.ServerTool{Tool: GetLabelTemplateTool, Handler: GetLabelTemplateFn})
Tool.RegisterRead(server.ServerTool{Tool: ListLicenseTemplatesTool, Handler: ListLicenseTemplatesFn})
Tool.RegisterRead(server.ServerTool{Tool: GetLicenseTemplateTool, Handler: GetLicenseTemplateFn})
Tool.RegisterRead(server.ServerTool{Tool: GetNodeInfoTool, Handler: GetNodeInfoFn})
Tool.RegisterRead(server.ServerTool{Tool: GetSigningKeyGPGTool, Handler: GetSigningKeyGPGFn})
Tool.RegisterRead(server.ServerTool{Tool: GetSigningKeySSHTool, Handler: GetSigningKeySSHFn})
Tool.RegisterWrite(server.ServerTool{Tool: RenderMarkdownTool, Handler: RenderMarkdownFn})
Tool.RegisterWrite(server.ServerTool{Tool: RenderMarkupTool, Handler: RenderMarkupFn})
}
func GetServerVersionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetServerVersionFn")
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
version, _, err := client.ServerVersion()
if err != nil {
return to.ErrorResult(fmt.Errorf("get server version err: %v", err))
}
return to.TextResult(map[string]string{"version": version})
}
func ListGitignoreTemplatesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListGitignoreTemplatesFn")
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
templates, _, err := client.ListGitignoresTemplates()
if err != nil {
return to.ErrorResult(fmt.Errorf("list gitignore templates err: %v", err))
}
return to.TextResult(templates)
}
func GetGitignoreTemplateFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetGitignoreTemplateFn")
name, _ := req.GetArguments()["name"].(string)
if name == "" {
return to.ErrorResult(errors.New("name is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
info, _, err := client.GetGitignoreTemplateInfo(name)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitignore template err: %v", err))
}
return to.TextResult(info)
}
func ListLabelTemplatesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListLabelTemplatesFn")
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
templates, _, err := client.ListLabelTemplates()
if err != nil {
return to.ErrorResult(fmt.Errorf("list label templates err: %v", err))
}
return to.TextResult(templates)
}
func GetLabelTemplateFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetLabelTemplateFn")
name, _ := req.GetArguments()["name"].(string)
if name == "" {
return to.ErrorResult(errors.New("name is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
labels, _, err := client.GetLabelTemplate(name)
if err != nil {
return to.ErrorResult(fmt.Errorf("get label template err: %v", err))
}
return to.TextResult(labels)
}
func ListLicenseTemplatesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListLicenseTemplatesFn")
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
templates, _, err := client.ListLicenseTemplates()
if err != nil {
return to.ErrorResult(fmt.Errorf("list license templates err: %v", err))
}
return to.TextResult(templates)
}
func GetLicenseTemplateFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetLicenseTemplateFn")
name, _ := req.GetArguments()["name"].(string)
if name == "" {
return to.ErrorResult(errors.New("name is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
info, _, err := client.GetLicenseTemplateInfo(name)
if err != nil {
return to.ErrorResult(fmt.Errorf("get license template err: %v", err))
}
return to.TextResult(info)
}
func RenderMarkdownFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called RenderMarkdownFn")
text, _ := req.GetArguments()["text"].(string)
if text == "" {
return to.ErrorResult(errors.New("text is required"))
}
opt := gitea_sdk.MarkdownOption{
Text: text,
}
if v, ok := req.GetArguments()["mode"].(string); ok {
opt.Mode = v
}
if v, ok := req.GetArguments()["context"].(string); ok {
opt.Context = v
}
if v, ok := req.GetArguments()["wiki"].(bool); ok {
opt.Wiki = v
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
html, _, err := client.RenderMarkdown(opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("render markdown err: %v", err))
}
return to.TextResult(map[string]string{"html": html})
}
func RenderMarkupFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called RenderMarkupFn")
text, _ := req.GetArguments()["text"].(string)
if text == "" {
return to.ErrorResult(errors.New("text is required"))
}
opt := gitea_sdk.MarkupOption{
Text: text,
}
if v, ok := req.GetArguments()["mode"].(string); ok {
opt.Mode = v
}
if v, ok := req.GetArguments()["context"].(string); ok {
opt.Context = v
}
if v, ok := req.GetArguments()["filepath"].(string); ok {
opt.FilePath = v
}
if v, ok := req.GetArguments()["wiki"].(bool); ok {
opt.Wiki = v
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
html, _, err := client.RenderMarkup(opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("render markup err: %v", err))
}
return to.TextResult(map[string]string{"html": html})
}
func GetNodeInfoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetNodeInfoFn")
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
info, _, err := client.GetNodeInfo()
if err != nil {
return to.ErrorResult(fmt.Errorf("get node info err: %v", err))
}
return to.TextResult(info)
}
func GetSigningKeyGPGFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetSigningKeyGPGFn")
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
key, _, err := client.GetSigningKeyGPG()
if err != nil {
return to.ErrorResult(fmt.Errorf("get signing key GPG err: %v", err))
}
return to.TextResult(map[string]string{"gpg_key": key})
}
func GetSigningKeySSHFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetSigningKeySSHFn")
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
key, _, err := client.GetSigningKeySSH()
if err != nil {
return to.ErrorResult(fmt.Errorf("get signing key SSH err: %v", err))
}
return to.TextResult(map[string]string{"ssh_key": key})
}

View File

@@ -0,0 +1,219 @@
package notification
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
"git.lethalbits.com/lethalbits/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 (
ListNotificationsToolName = "list_notifications"
CheckNewNotificationsToolName = "check_new_notifications"
GetNotificationToolName = "get_notification"
ReadNotificationToolName = "read_notification"
ReadAllNotificationsToolName = "read_all_notifications"
ListRepoNotificationsToolName = "list_repo_notifications"
ReadRepoNotificationsToolName = "read_repo_notifications"
)
var (
ListNotificationsTool = mcp.NewTool(
ListNotificationsToolName,
mcp.WithDescription("List the authenticated user's notifications"),
mcp.WithString("status", mcp.Description("filter by status: read, unread, pinned")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
CheckNewNotificationsTool = mcp.NewTool(
CheckNewNotificationsToolName,
mcp.WithDescription("Check if there are new notifications"),
)
GetNotificationTool = mcp.NewTool(
GetNotificationToolName,
mcp.WithDescription("Get a notification thread by ID"),
mcp.WithNumber("id", mcp.Required(), mcp.Description("notification thread ID")),
)
ReadNotificationTool = mcp.NewTool(
ReadNotificationToolName,
mcp.WithDescription("Mark a notification as read"),
mcp.WithNumber("id", mcp.Required(), mcp.Description("notification thread ID")),
)
ReadAllNotificationsTool = mcp.NewTool(
ReadAllNotificationsToolName,
mcp.WithDescription("Mark all notifications as read"),
)
ListRepoNotificationsTool = mcp.NewTool(
ListRepoNotificationsToolName,
mcp.WithDescription("List notifications for a repository"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("status", mcp.Description("filter by status: read, unread, pinned")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
ReadRepoNotificationsTool = mcp.NewTool(
ReadRepoNotificationsToolName,
mcp.WithDescription("Mark all notifications in a repository as read"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: ListNotificationsTool, Handler: ListNotificationsFn})
Tool.RegisterRead(server.ServerTool{Tool: CheckNewNotificationsTool, Handler: CheckNewNotificationsFn})
Tool.RegisterRead(server.ServerTool{Tool: GetNotificationTool, Handler: GetNotificationFn})
Tool.RegisterRead(server.ServerTool{Tool: ListRepoNotificationsTool, Handler: ListRepoNotificationsFn})
Tool.RegisterWrite(server.ServerTool{Tool: ReadNotificationTool, Handler: ReadNotificationFn})
Tool.RegisterWrite(server.ServerTool{Tool: ReadAllNotificationsTool, Handler: ReadAllNotificationsFn})
Tool.RegisterWrite(server.ServerTool{Tool: ReadRepoNotificationsTool, Handler: ReadRepoNotificationsFn})
}
func ListNotificationsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListNotificationsFn")
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
opt := gitea_sdk.ListNotificationOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
}
if v, ok := req.GetArguments()["status"].(string); ok {
opt.Status = []gitea_sdk.NotifyStatus{gitea_sdk.NotifyStatus(v)}
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
notifications, _, err := client.ListNotifications(opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("list notifications err: %v", err))
}
return to.TextResult(notifications)
}
func CheckNewNotificationsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CheckNewNotificationsFn")
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
count, _, err := client.CheckNotifications()
if err != nil {
return to.ErrorResult(fmt.Errorf("check notifications err: %v", err))
}
return to.TextResult(map[string]int64{"new_notifications": count})
}
func GetNotificationFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetNotificationFn")
id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
notification, _, err := client.GetNotification(id)
if err != nil {
return to.ErrorResult(fmt.Errorf("get notification %d err: %v", id, err))
}
return to.TextResult(notification)
}
func ReadNotificationFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ReadNotificationFn")
id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
notification, _, err := client.ReadNotification(id)
if err != nil {
return to.ErrorResult(fmt.Errorf("read notification %d err: %v", id, err))
}
return to.TextResult(notification)
}
func ReadAllNotificationsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ReadAllNotificationsFn")
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
notifications, _, err := client.ReadNotifications(gitea_sdk.MarkNotificationOptions{})
if err != nil {
return to.ErrorResult(fmt.Errorf("read all notifications err: %v", err))
}
return to.TextResult(notifications)
}
func ListRepoNotificationsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListRepoNotificationsFn")
owner, ok := req.GetArguments()["owner"].(string)
if !ok {
return to.ErrorResult(errors.New("owner is required"))
}
repo, ok := req.GetArguments()["repo"].(string)
if !ok {
return to.ErrorResult(errors.New("repo is required"))
}
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
opt := gitea_sdk.ListNotificationOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
}
if v, ok := req.GetArguments()["status"].(string); ok {
opt.Status = []gitea_sdk.NotifyStatus{gitea_sdk.NotifyStatus(v)}
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
notifications, _, err := client.ListRepoNotifications(owner, repo, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("list %s/%s notifications err: %v", owner, repo, err))
}
return to.TextResult(notifications)
}
func ReadRepoNotificationsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ReadRepoNotificationsFn")
owner, ok := req.GetArguments()["owner"].(string)
if !ok {
return to.ErrorResult(errors.New("owner is required"))
}
repo, ok := req.GetArguments()["repo"].(string)
if !ok {
return to.ErrorResult(errors.New("repo is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
notifications, _, err := client.ReadRepoNotifications(owner, repo, gitea_sdk.MarkNotificationOptions{})
if err != nil {
return to.ErrorResult(fmt.Errorf("read %s/%s notifications err: %v", owner, repo, err))
}
return to.TextResult(notifications)
}

View File

@@ -10,20 +10,26 @@ import (
"syscall" "syscall"
"time" "time"
"gitea.com/gitea/gitea-mcp/operation/actions" "git.lethalbits.com/lethalbits/gitea-mcp/operation/actions"
"gitea.com/gitea/gitea-mcp/operation/issue" "git.lethalbits.com/lethalbits/gitea-mcp/operation/admin"
"gitea.com/gitea/gitea-mcp/operation/label" "git.lethalbits.com/lethalbits/gitea-mcp/operation/issue"
"gitea.com/gitea/gitea-mcp/operation/milestone" "git.lethalbits.com/lethalbits/gitea-mcp/operation/label"
"gitea.com/gitea/gitea-mcp/operation/pull" "git.lethalbits.com/lethalbits/gitea-mcp/operation/milestone"
"gitea.com/gitea/gitea-mcp/operation/repo" "git.lethalbits.com/lethalbits/gitea-mcp/operation/miscellaneous"
"gitea.com/gitea/gitea-mcp/operation/search" "git.lethalbits.com/lethalbits/gitea-mcp/operation/notification"
"gitea.com/gitea/gitea-mcp/operation/timetracking" "git.lethalbits.com/lethalbits/gitea-mcp/operation/organization"
"gitea.com/gitea/gitea-mcp/operation/user" "git.lethalbits.com/lethalbits/gitea-mcp/operation/packages"
"gitea.com/gitea/gitea-mcp/operation/version" "git.lethalbits.com/lethalbits/gitea-mcp/operation/pull"
"gitea.com/gitea/gitea-mcp/operation/wiki" "git.lethalbits.com/lethalbits/gitea-mcp/operation/repo"
mcpContext "gitea.com/gitea/gitea-mcp/pkg/context" "git.lethalbits.com/lethalbits/gitea-mcp/operation/search"
"gitea.com/gitea/gitea-mcp/pkg/flag" "git.lethalbits.com/lethalbits/gitea-mcp/operation/settings"
"gitea.com/gitea/gitea-mcp/pkg/log" "git.lethalbits.com/lethalbits/gitea-mcp/operation/timetracking"
"git.lethalbits.com/lethalbits/gitea-mcp/operation/user"
"git.lethalbits.com/lethalbits/gitea-mcp/operation/version"
"git.lethalbits.com/lethalbits/gitea-mcp/operation/wiki"
mcpContext "git.lethalbits.com/lethalbits/gitea-mcp/pkg/context"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/flag"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"
) )
@@ -64,6 +70,24 @@ func RegisterTool(s *server.MCPServer) {
// Time Tracking Tool // Time Tracking Tool
s.AddTools(timetracking.Tool.Tools()...) s.AddTools(timetracking.Tool.Tools()...)
// Organization Tool
s.AddTools(organization.Tool.Tools()...)
// Notification Tool
s.AddTools(notification.Tool.Tools()...)
// Settings Tool
s.AddTools(settings.Tool.Tools()...)
// Package Tool
s.AddTools(packages.Tool.Tools()...)
// Miscellaneous Tool
s.AddTools(miscellaneous.Tool.Tools()...)
// Admin Tool
s.AddTools(admin.Tool.Tools()...)
s.DeleteTools("") s.DeleteTools("")
} }

View File

@@ -0,0 +1,86 @@
package organization
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
ListOrgActivityFeedsToolName = "list_org_activity_feeds"
ListTeamActivityFeedsToolName = "list_team_activity_feeds"
)
var (
ListOrgActivityFeedsTool = mcp.NewTool(
ListOrgActivityFeedsToolName,
mcp.WithDescription("List an organization's activity feeds"),
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
ListTeamActivityFeedsTool = mcp.NewTool(
ListTeamActivityFeedsToolName,
mcp.WithDescription("List a team's activity feeds"),
mcp.WithNumber("id", mcp.Required(), mcp.Description("team ID")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: ListOrgActivityFeedsTool, Handler: ListOrgActivityFeedsFn})
Tool.RegisterRead(server.ServerTool{Tool: ListTeamActivityFeedsTool, Handler: ListTeamActivityFeedsFn})
}
func ListOrgActivityFeedsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListOrgActivityFeedsFn")
org, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("org is required"))
}
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
feeds, _, err := client.ListOrgActivityFeeds(org, gitea_sdk.ListOrgActivityFeedsOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list org %s activity feeds err: %v", org, err))
}
return to.TextResult(feeds)
}
func ListTeamActivityFeedsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListTeamActivityFeedsFn")
id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil {
return to.ErrorResult(err)
}
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
feeds, _, err := client.ListTeamActivityFeeds(id, gitea_sdk.ListTeamActivityFeedsOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list team %d activity feeds err: %v", id, err))
}
return to.TextResult(feeds)
}

View File

@@ -0,0 +1,145 @@
package organization
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
ListOrgBlocksToolName = "list_org_blocks"
CheckOrgBlockToolName = "check_org_block"
BlockOrgUserToolName = "block_org_user"
UnblockOrgUserToolName = "unblock_org_user"
)
var (
ListOrgBlocksTool = mcp.NewTool(
ListOrgBlocksToolName,
mcp.WithDescription("List users blocked by an organization"),
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
CheckOrgBlockTool = mcp.NewTool(
CheckOrgBlockToolName,
mcp.WithDescription("Check if a user is blocked by an organization"),
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
mcp.WithString("username", mcp.Required(), mcp.Description("username to check")),
)
BlockOrgUserTool = mcp.NewTool(
BlockOrgUserToolName,
mcp.WithDescription("Block a user from an organization"),
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
mcp.WithString("username", mcp.Required(), mcp.Description("username to block")),
)
UnblockOrgUserTool = mcp.NewTool(
UnblockOrgUserToolName,
mcp.WithDescription("Unblock a user from an organization"),
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
mcp.WithString("username", mcp.Required(), mcp.Description("username to unblock")),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: ListOrgBlocksTool, Handler: ListOrgBlocksFn})
Tool.RegisterRead(server.ServerTool{Tool: CheckOrgBlockTool, Handler: CheckOrgBlockFn})
Tool.RegisterWrite(server.ServerTool{Tool: BlockOrgUserTool, Handler: BlockOrgUserFn})
Tool.RegisterWrite(server.ServerTool{Tool: UnblockOrgUserTool, Handler: UnblockOrgUserFn})
}
func ListOrgBlocksFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListOrgBlocksFn")
org, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("org is required"))
}
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
users, _, err := client.ListOrgBlocks(org, gitea_sdk.ListOrgBlocksOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list org %s blocks err: %v", org, err))
}
return to.TextResult(users)
}
func CheckOrgBlockFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CheckOrgBlockFn")
org, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("org is required"))
}
username, ok := req.GetArguments()["username"].(string)
if !ok {
return to.ErrorResult(errors.New("username is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
isBlocked, _, err := client.CheckOrgBlock(org, username)
if err != nil {
return to.ErrorResult(fmt.Errorf("check org %s block for %s err: %v", org, username, err))
}
return to.TextResult(map[string]any{"org": org, "username": username, "is_blocked": isBlocked})
}
func BlockOrgUserFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called BlockOrgUserFn")
org, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("org is required"))
}
username, ok := req.GetArguments()["username"].(string)
if !ok {
return to.ErrorResult(errors.New("username is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.BlockOrgUser(org, username)
if err != nil {
return to.ErrorResult(fmt.Errorf("block user %s from org %s err: %v", username, org, err))
}
return to.TextResult(map[string]string{"status": "blocked", "org": org, "username": username})
}
func UnblockOrgUserFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called UnblockOrgUserFn")
org, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("org is required"))
}
username, ok := req.GetArguments()["username"].(string)
if !ok {
return to.ErrorResult(errors.New("username is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.UnblockOrgUser(org, username)
if err != nil {
return to.ErrorResult(fmt.Errorf("unblock user %s from org %s err: %v", username, org, err))
}
return to.TextResult(map[string]string{"status": "unblocked", "org": org, "username": username})
}

View File

@@ -0,0 +1,251 @@
package organization
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
ListOrgHooksToolName = "list_org_hooks"
GetOrgHookToolName = "get_org_hook"
CreateOrgHookToolName = "create_org_hook"
EditOrgHookToolName = "edit_org_hook"
DeleteOrgHookToolName = "delete_org_hook"
)
var (
ListOrgHooksTool = mcp.NewTool(
ListOrgHooksToolName,
mcp.WithDescription("List an organization's webhooks"),
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
GetOrgHookTool = mcp.NewTool(
GetOrgHookToolName,
mcp.WithDescription("Get an organization webhook by ID"),
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
mcp.WithNumber("id", mcp.Required(), mcp.Description("webhook ID")),
)
CreateOrgHookTool = mcp.NewTool(
CreateOrgHookToolName,
mcp.WithDescription("Create an organization webhook"),
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
mcp.WithString("type", mcp.Required(), mcp.Description("hook type: gitea, slack, discord, dingtalk, telegram, msteams, feishu, matrix, wechatwork, packagist")),
mcp.WithString("url", mcp.Required(), mcp.Description("target URL for the webhook")),
mcp.WithString("content_type", mcp.Description("content type: json or form"), mcp.DefaultString("json")),
mcp.WithString("secret", mcp.Description("webhook secret")),
mcp.WithBoolean("active", mcp.Description("whether the webhook is active (default: true)")),
mcp.WithArray("events", mcp.Description("list of events to trigger on"), mcp.Items(map[string]any{"type": "string"})),
)
EditOrgHookTool = mcp.NewTool(
EditOrgHookToolName,
mcp.WithDescription("Edit an organization webhook"),
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
mcp.WithNumber("id", mcp.Required(), mcp.Description("webhook ID")),
mcp.WithString("url", mcp.Description("target URL")),
mcp.WithString("content_type", mcp.Description("content type: json or form")),
mcp.WithString("secret", mcp.Description("webhook secret")),
mcp.WithBoolean("active", mcp.Description("whether the webhook is active")),
mcp.WithArray("events", mcp.Description("list of events to trigger on"), mcp.Items(map[string]any{"type": "string"})),
)
DeleteOrgHookTool = mcp.NewTool(
DeleteOrgHookToolName,
mcp.WithDescription("Delete an organization webhook"),
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
mcp.WithNumber("id", mcp.Required(), mcp.Description("webhook ID")),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: ListOrgHooksTool, Handler: ListOrgHooksFn})
Tool.RegisterRead(server.ServerTool{Tool: GetOrgHookTool, Handler: GetOrgHookFn})
Tool.RegisterWrite(server.ServerTool{Tool: CreateOrgHookTool, Handler: CreateOrgHookFn})
Tool.RegisterWrite(server.ServerTool{Tool: EditOrgHookTool, Handler: EditOrgHookFn})
Tool.RegisterWrite(server.ServerTool{Tool: DeleteOrgHookTool, Handler: DeleteOrgHookFn})
}
func ListOrgHooksFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListOrgHooksFn")
org, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("org is required"))
}
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
hooks, _, err := client.ListOrgHooks(org, gitea_sdk.ListHooksOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list org %s hooks err: %v", org, err))
}
return to.TextResult(hooks)
}
func GetOrgHookFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetOrgHookFn")
org, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("org is required"))
}
id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
hook, _, err := client.GetOrgHook(org, id)
if err != nil {
return to.ErrorResult(fmt.Errorf("get org %s hook %d err: %v", org, id, err))
}
return to.TextResult(hook)
}
func CreateOrgHookFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateOrgHookFn")
org, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("org is required"))
}
hookType, ok := req.GetArguments()["type"].(string)
if !ok {
return to.ErrorResult(errors.New("type is required"))
}
hookURL, ok := req.GetArguments()["url"].(string)
if !ok {
return to.ErrorResult(errors.New("url is required"))
}
contentType := "json"
if v, ok := req.GetArguments()["content_type"].(string); ok {
contentType = v
}
config := map[string]string{
"url": hookURL,
"content_type": contentType,
}
if v, ok := req.GetArguments()["secret"].(string); ok {
config["secret"] = v
}
opt := gitea_sdk.CreateHookOption{
Type: gitea_sdk.HookType(hookType),
Config: config,
Active: true,
}
if v, ok := req.GetArguments()["active"].(bool); ok {
opt.Active = v
}
if eventsArg, exists := req.GetArguments()["events"]; exists {
if eventsSlice, ok := eventsArg.([]any); ok {
events := make([]string, 0, len(eventsSlice))
for _, e := range eventsSlice {
if s, ok := e.(string); ok {
events = append(events, s)
}
}
opt.Events = events
}
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
hook, _, err := client.CreateOrgHook(org, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("create org %s hook err: %v", org, err))
}
return to.TextResult(hook)
}
func EditOrgHookFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called EditOrgHookFn")
org, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("org is required"))
}
id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil {
return to.ErrorResult(err)
}
opt := gitea_sdk.EditHookOption{}
config := map[string]string{}
if v, ok := req.GetArguments()["url"].(string); ok {
config["url"] = v
}
if v, ok := req.GetArguments()["content_type"].(string); ok {
config["content_type"] = v
}
if v, ok := req.GetArguments()["secret"].(string); ok {
config["secret"] = v
}
if len(config) > 0 {
opt.Config = config
}
if v, ok := req.GetArguments()["active"].(bool); ok {
opt.Active = &v
}
if eventsArg, exists := req.GetArguments()["events"]; exists {
if eventsSlice, ok := eventsArg.([]any); ok {
events := make([]string, 0, len(eventsSlice))
for _, e := range eventsSlice {
if s, ok := e.(string); ok {
events = append(events, s)
}
}
opt.Events = events
}
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.EditOrgHook(org, id, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("edit org %s hook %d err: %v", org, id, err))
}
hook, _, err := client.GetOrgHook(org, id)
if err != nil {
return to.ErrorResult(fmt.Errorf("get org %s hook %d after edit err: %v", org, id, err))
}
return to.TextResult(hook)
}
func DeleteOrgHookFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteOrgHookFn")
org, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("org is required"))
}
id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.DeleteOrgHook(org, id)
if err != nil {
return to.ErrorResult(fmt.Errorf("delete org %s hook %d err: %v", org, id, err))
}
return to.TextResult(map[string]any{"status": "deleted", "org": org, "hook_id": id})
}

View File

@@ -0,0 +1,241 @@
package organization
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
ListOrgMembersToolName = "list_org_members"
CheckOrgMembershipToolName = "check_org_membership"
RemoveOrgMemberToolName = "remove_org_member"
ListOrgPublicMembersToolName = "list_org_public_members"
CheckOrgPublicMemberToolName = "check_org_public_member"
SetOrgPublicMemberToolName = "set_org_public_member"
GetOrgPermissionsToolName = "get_org_permissions"
)
var (
ListOrgMembersTool = mcp.NewTool(
ListOrgMembersToolName,
mcp.WithDescription("List an organization's members"),
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
CheckOrgMembershipTool = mcp.NewTool(
CheckOrgMembershipToolName,
mcp.WithDescription("Check if a user is a member of an organization"),
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
mcp.WithString("username", mcp.Required(), mcp.Description("username to check")),
)
RemoveOrgMemberTool = mcp.NewTool(
RemoveOrgMemberToolName,
mcp.WithDescription("Remove a member from an organization"),
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
mcp.WithString("username", mcp.Required(), mcp.Description("username to remove")),
)
ListOrgPublicMembersTool = mcp.NewTool(
ListOrgPublicMembersToolName,
mcp.WithDescription("List an organization's public members"),
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
CheckOrgPublicMemberTool = mcp.NewTool(
CheckOrgPublicMemberToolName,
mcp.WithDescription("Check if a user is a public member of an organization"),
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
mcp.WithString("username", mcp.Required(), mcp.Description("username to check")),
)
SetOrgPublicMemberTool = mcp.NewTool(
SetOrgPublicMemberToolName,
mcp.WithDescription("Set or remove a user's public membership in an organization"),
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
mcp.WithString("username", mcp.Required(), mcp.Description("username")),
mcp.WithBoolean("visible", mcp.Required(), mcp.Description("true to make public, false to make private")),
)
GetOrgPermissionsTool = mcp.NewTool(
GetOrgPermissionsToolName,
mcp.WithDescription("Get a user's permissions in an organization"),
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
mcp.WithString("username", mcp.Required(), mcp.Description("username")),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: ListOrgMembersTool, Handler: ListOrgMembersFn})
Tool.RegisterRead(server.ServerTool{Tool: CheckOrgMembershipTool, Handler: CheckOrgMembershipFn})
Tool.RegisterRead(server.ServerTool{Tool: ListOrgPublicMembersTool, Handler: ListOrgPublicMembersFn})
Tool.RegisterRead(server.ServerTool{Tool: CheckOrgPublicMemberTool, Handler: CheckOrgPublicMemberFn})
Tool.RegisterRead(server.ServerTool{Tool: GetOrgPermissionsTool, Handler: GetOrgPermissionsFn})
Tool.RegisterWrite(server.ServerTool{Tool: RemoveOrgMemberTool, Handler: RemoveOrgMemberFn})
Tool.RegisterWrite(server.ServerTool{Tool: SetOrgPublicMemberTool, Handler: SetOrgPublicMemberFn})
}
func ListOrgMembersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListOrgMembersFn")
org, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("org is required"))
}
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
members, _, err := client.ListOrgMembership(org, gitea_sdk.ListOrgMembershipOption{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list org %s members err: %v", org, err))
}
return to.TextResult(members)
}
func CheckOrgMembershipFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CheckOrgMembershipFn")
org, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("org is required"))
}
username, ok := req.GetArguments()["username"].(string)
if !ok {
return to.ErrorResult(errors.New("username is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
isMember, _, err := client.CheckOrgMembership(org, username)
if err != nil {
return to.ErrorResult(fmt.Errorf("check org %s membership for %s err: %v", org, username, err))
}
return to.TextResult(map[string]any{"org": org, "username": username, "is_member": isMember})
}
func RemoveOrgMemberFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called RemoveOrgMemberFn")
org, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("org is required"))
}
username, ok := req.GetArguments()["username"].(string)
if !ok {
return to.ErrorResult(errors.New("username is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.DeleteOrgMembership(org, username)
if err != nil {
return to.ErrorResult(fmt.Errorf("remove member %s from org %s err: %v", username, org, err))
}
return to.TextResult(map[string]string{"status": "removed", "org": org, "username": username})
}
func ListOrgPublicMembersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListOrgPublicMembersFn")
org, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("org is required"))
}
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
members, _, err := client.ListPublicOrgMembership(org, gitea_sdk.ListOrgMembershipOption{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list org %s public members err: %v", org, err))
}
return to.TextResult(members)
}
func CheckOrgPublicMemberFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CheckOrgPublicMemberFn")
org, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("org is required"))
}
username, ok := req.GetArguments()["username"].(string)
if !ok {
return to.ErrorResult(errors.New("username is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
isPublic, _, err := client.CheckPublicOrgMembership(org, username)
if err != nil {
return to.ErrorResult(fmt.Errorf("check org %s public membership for %s err: %v", org, username, err))
}
return to.TextResult(map[string]any{"org": org, "username": username, "is_public_member": isPublic})
}
func SetOrgPublicMemberFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called SetOrgPublicMemberFn")
org, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("org is required"))
}
username, ok := req.GetArguments()["username"].(string)
if !ok {
return to.ErrorResult(errors.New("username is required"))
}
visible, ok := req.GetArguments()["visible"].(bool)
if !ok {
return to.ErrorResult(errors.New("visible is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.SetPublicOrgMembership(org, username, visible)
if err != nil {
return to.ErrorResult(fmt.Errorf("set public membership for %s in org %s err: %v", username, org, err))
}
return to.TextResult(map[string]any{"org": org, "username": username, "visible": visible})
}
func GetOrgPermissionsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetOrgPermissionsFn")
org, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("org is required"))
}
username, ok := req.GetArguments()["username"].(string)
if !ok {
return to.ErrorResult(errors.New("username is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
perms, _, err := client.GetOrgPermissions(org, username)
if err != nil {
return to.ErrorResult(fmt.Errorf("get permissions for %s in org %s err: %v", username, org, err))
}
return to.TextResult(perms)
}

View File

@@ -0,0 +1,319 @@
package organization
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
"git.lethalbits.com/lethalbits/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 (
ListOrgsToolName = "list_orgs"
GetOrgToolName = "get_org"
CreateOrgToolName = "create_org"
EditOrgToolName = "edit_org"
DeleteOrgToolName = "delete_org"
RenameOrgToolName = "rename_org"
ListOrgReposToolName = "list_org_repos"
CreateOrgRepoToolName = "create_org_repo"
)
var (
ListOrgsTool = mcp.NewTool(
ListOrgsToolName,
mcp.WithDescription("List all visible organizations"),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
GetOrgTool = mcp.NewTool(
GetOrgToolName,
mcp.WithDescription("Get an organization by name"),
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
)
CreateOrgTool = mcp.NewTool(
CreateOrgToolName,
mcp.WithDescription("Create an organization"),
mcp.WithString("username", mcp.Required(), mcp.Description("organization username")),
mcp.WithString("full_name", mcp.Description("organization full name")),
mcp.WithString("description", mcp.Description("organization description")),
mcp.WithString("website", mcp.Description("organization website")),
mcp.WithString("location", mcp.Description("organization location")),
mcp.WithString("visibility", mcp.Description("visibility: public, limited, or private"), mcp.DefaultString("public")),
)
EditOrgTool = mcp.NewTool(
EditOrgToolName,
mcp.WithDescription("Edit an organization"),
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
mcp.WithString("full_name", mcp.Description("organization full name")),
mcp.WithString("description", mcp.Description("organization description")),
mcp.WithString("website", mcp.Description("organization website")),
mcp.WithString("location", mcp.Description("organization location")),
mcp.WithString("visibility", mcp.Description("visibility: public, limited, or private")),
)
DeleteOrgTool = mcp.NewTool(
DeleteOrgToolName,
mcp.WithDescription("Delete an organization"),
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
)
RenameOrgTool = mcp.NewTool(
RenameOrgToolName,
mcp.WithDescription("Rename an organization"),
mcp.WithString("org", mcp.Required(), mcp.Description("current organization name")),
mcp.WithString("new_name", mcp.Required(), mcp.Description("new organization name")),
)
ListOrgReposTool = mcp.NewTool(
ListOrgReposToolName,
mcp.WithDescription("List an organization's repositories"),
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
CreateOrgRepoTool = mcp.NewTool(
CreateOrgRepoToolName,
mcp.WithDescription("Create a repository in an organization"),
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
mcp.WithString("name", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("description", mcp.Description("repository description")),
mcp.WithBoolean("private", mcp.Description("whether the repository is private")),
mcp.WithBoolean("auto_init", mcp.Description("whether to auto-initialize with README")),
mcp.WithString("default_branch", mcp.Description("default branch name")),
mcp.WithString("gitignores", mcp.Description("gitignore template")),
mcp.WithString("license", mcp.Description("license template")),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: ListOrgsTool, Handler: ListOrgsFn})
Tool.RegisterRead(server.ServerTool{Tool: GetOrgTool, Handler: GetOrgFn})
Tool.RegisterRead(server.ServerTool{Tool: ListOrgReposTool, Handler: ListOrgReposFn})
Tool.RegisterWrite(server.ServerTool{Tool: CreateOrgTool, Handler: CreateOrgFn})
Tool.RegisterWrite(server.ServerTool{Tool: EditOrgTool, Handler: EditOrgFn})
Tool.RegisterWrite(server.ServerTool{Tool: DeleteOrgTool, Handler: DeleteOrgFn})
Tool.RegisterWrite(server.ServerTool{Tool: RenameOrgTool, Handler: RenameOrgFn})
Tool.RegisterWrite(server.ServerTool{Tool: CreateOrgRepoTool, Handler: CreateOrgRepoFn})
}
func ListOrgsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListOrgsFn")
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
orgs, _, err := client.ListOrgs(gitea_sdk.ListOrgsOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list orgs err: %v", err))
}
return to.TextResult(orgs)
}
func GetOrgFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetOrgFn")
org, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("org is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
organization, _, err := client.GetOrg(org)
if err != nil {
return to.ErrorResult(fmt.Errorf("get org %s err: %v", org, err))
}
return to.TextResult(organization)
}
func CreateOrgFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateOrgFn")
username, ok := req.GetArguments()["username"].(string)
if !ok {
return to.ErrorResult(errors.New("username is required"))
}
opt := gitea_sdk.CreateOrgOption{
Name: username,
}
if v, ok := req.GetArguments()["full_name"].(string); ok {
opt.FullName = v
}
if v, ok := req.GetArguments()["description"].(string); ok {
opt.Description = v
}
if v, ok := req.GetArguments()["website"].(string); ok {
opt.Website = v
}
if v, ok := req.GetArguments()["location"].(string); ok {
opt.Location = v
}
if v, ok := req.GetArguments()["visibility"].(string); ok {
opt.Visibility = gitea_sdk.VisibleType(v)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
organization, _, err := client.CreateOrg(opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("create org %s err: %v", username, err))
}
return to.TextResult(organization)
}
func EditOrgFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called EditOrgFn")
org, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("org is required"))
}
opt := gitea_sdk.EditOrgOption{}
if v, ok := req.GetArguments()["full_name"].(string); ok {
opt.FullName = v
}
if v, ok := req.GetArguments()["description"].(string); ok {
opt.Description = v
}
if v, ok := req.GetArguments()["website"].(string); ok {
opt.Website = v
}
if v, ok := req.GetArguments()["location"].(string); ok {
opt.Location = v
}
if v, ok := req.GetArguments()["visibility"].(string); ok {
opt.Visibility = gitea_sdk.VisibleType(v)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.EditOrg(org, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("edit org %s err: %v", org, err))
}
updated, _, err := client.GetOrg(org)
if err != nil {
return to.ErrorResult(fmt.Errorf("get org %s after edit err: %v", org, err))
}
return to.TextResult(updated)
}
func DeleteOrgFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteOrgFn")
org, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("org is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.DeleteOrg(org)
if err != nil {
return to.ErrorResult(fmt.Errorf("delete org %s err: %v", org, err))
}
return to.TextResult(map[string]string{"status": "deleted", "org": org})
}
func RenameOrgFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called RenameOrgFn")
org, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("org is required"))
}
newName, ok := req.GetArguments()["new_name"].(string)
if !ok {
return to.ErrorResult(errors.New("new_name is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.RenameOrg(org, gitea_sdk.RenameOrgOption{NewName: newName})
if err != nil {
return to.ErrorResult(fmt.Errorf("rename org %s to %s err: %v", org, newName, err))
}
return to.TextResult(map[string]string{"status": "renamed", "old_name": org, "new_name": newName})
}
func ListOrgReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListOrgReposFn")
org, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("org is required"))
}
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
repos, _, err := client.ListOrgRepos(org, gitea_sdk.ListOrgReposOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list org %s repos err: %v", org, err))
}
return to.TextResult(repos)
}
func CreateOrgRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateOrgRepoFn")
org, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("org is required"))
}
name, ok := req.GetArguments()["name"].(string)
if !ok {
return to.ErrorResult(errors.New("name is required"))
}
opt := gitea_sdk.CreateRepoOption{
Name: name,
}
if v, ok := req.GetArguments()["description"].(string); ok {
opt.Description = v
}
if v, ok := req.GetArguments()["private"].(bool); ok {
opt.Private = v
}
if v, ok := req.GetArguments()["auto_init"].(bool); ok {
opt.AutoInit = v
}
if v, ok := req.GetArguments()["default_branch"].(string); ok {
opt.DefaultBranch = v
}
if v, ok := req.GetArguments()["gitignores"].(string); ok {
opt.Gitignores = v
}
if v, ok := req.GetArguments()["license"].(string); ok {
opt.License = v
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
repo, _, err := client.CreateOrgRepo(org, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("create repo %s/%s err: %v", org, name, err))
}
return to.TextResult(repo)
}

View File

@@ -0,0 +1,427 @@
package organization
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
ListOrgTeamsToolName = "list_org_teams"
GetTeamToolName = "get_team"
CreateTeamToolName = "create_team"
EditTeamToolName = "edit_team"
DeleteTeamToolName = "delete_team"
ListTeamMembersToolName = "list_team_members"
GetTeamMemberToolName = "get_team_member"
AddTeamMemberToolName = "add_team_member"
RemoveTeamMemberToolName = "remove_team_member"
ListTeamReposToolName = "list_team_repos"
AddTeamRepoToolName = "add_team_repo"
RemoveTeamRepoToolName = "remove_team_repo"
)
var (
ListOrgTeamsTool = mcp.NewTool(
ListOrgTeamsToolName,
mcp.WithDescription("List an organization's teams"),
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
GetTeamTool = mcp.NewTool(
GetTeamToolName,
mcp.WithDescription("Get a team by ID"),
mcp.WithNumber("id", mcp.Required(), mcp.Description("team ID")),
)
CreateTeamTool = mcp.NewTool(
CreateTeamToolName,
mcp.WithDescription("Create a team in an organization"),
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
mcp.WithString("name", mcp.Required(), mcp.Description("team name")),
mcp.WithString("description", mcp.Description("team description")),
mcp.WithString("permission", mcp.Description("team permission: read, write, admin"), mcp.DefaultString("read")),
mcp.WithBoolean("can_create_org_repo", mcp.Description("whether team members can create repos in the org")),
mcp.WithBoolean("includes_all_repositories", mcp.Description("whether team has access to all repos")),
)
EditTeamTool = mcp.NewTool(
EditTeamToolName,
mcp.WithDescription("Edit a team"),
mcp.WithNumber("id", mcp.Required(), mcp.Description("team ID")),
mcp.WithString("name", mcp.Description("team name")),
mcp.WithString("description", mcp.Description("team description")),
mcp.WithString("permission", mcp.Description("team permission: read, write, admin")),
mcp.WithBoolean("can_create_org_repo", mcp.Description("whether team members can create repos in the org")),
mcp.WithBoolean("includes_all_repositories", mcp.Description("whether team has access to all repos")),
)
DeleteTeamTool = mcp.NewTool(
DeleteTeamToolName,
mcp.WithDescription("Delete a team"),
mcp.WithNumber("id", mcp.Required(), mcp.Description("team ID")),
)
ListTeamMembersTool = mcp.NewTool(
ListTeamMembersToolName,
mcp.WithDescription("List a team's members"),
mcp.WithNumber("id", mcp.Required(), mcp.Description("team ID")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
GetTeamMemberTool = mcp.NewTool(
GetTeamMemberToolName,
mcp.WithDescription("Get a specific member of a team"),
mcp.WithNumber("id", mcp.Required(), mcp.Description("team ID")),
mcp.WithString("username", mcp.Required(), mcp.Description("username")),
)
AddTeamMemberTool = mcp.NewTool(
AddTeamMemberToolName,
mcp.WithDescription("Add a member to a team"),
mcp.WithNumber("id", mcp.Required(), mcp.Description("team ID")),
mcp.WithString("username", mcp.Required(), mcp.Description("username to add")),
)
RemoveTeamMemberTool = mcp.NewTool(
RemoveTeamMemberToolName,
mcp.WithDescription("Remove a member from a team"),
mcp.WithNumber("id", mcp.Required(), mcp.Description("team ID")),
mcp.WithString("username", mcp.Required(), mcp.Description("username to remove")),
)
ListTeamReposTool = mcp.NewTool(
ListTeamReposToolName,
mcp.WithDescription("List a team's repositories"),
mcp.WithNumber("id", mcp.Required(), mcp.Description("team ID")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
AddTeamRepoTool = mcp.NewTool(
AddTeamRepoToolName,
mcp.WithDescription("Add a repository to a team"),
mcp.WithNumber("id", mcp.Required(), mcp.Description("team ID")),
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
)
RemoveTeamRepoTool = mcp.NewTool(
RemoveTeamRepoToolName,
mcp.WithDescription("Remove a repository from a team"),
mcp.WithNumber("id", mcp.Required(), mcp.Description("team ID")),
mcp.WithString("org", mcp.Required(), mcp.Description("organization name")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: ListOrgTeamsTool, Handler: ListOrgTeamsFn})
Tool.RegisterRead(server.ServerTool{Tool: GetTeamTool, Handler: GetTeamFn})
Tool.RegisterRead(server.ServerTool{Tool: ListTeamMembersTool, Handler: ListTeamMembersFn})
Tool.RegisterRead(server.ServerTool{Tool: GetTeamMemberTool, Handler: GetTeamMemberFn})
Tool.RegisterRead(server.ServerTool{Tool: ListTeamReposTool, Handler: ListTeamReposFn})
Tool.RegisterWrite(server.ServerTool{Tool: CreateTeamTool, Handler: CreateTeamFn})
Tool.RegisterWrite(server.ServerTool{Tool: EditTeamTool, Handler: EditTeamFn})
Tool.RegisterWrite(server.ServerTool{Tool: DeleteTeamTool, Handler: DeleteTeamFn})
Tool.RegisterWrite(server.ServerTool{Tool: AddTeamMemberTool, Handler: AddTeamMemberFn})
Tool.RegisterWrite(server.ServerTool{Tool: RemoveTeamMemberTool, Handler: RemoveTeamMemberFn})
Tool.RegisterWrite(server.ServerTool{Tool: AddTeamRepoTool, Handler: AddTeamRepoFn})
Tool.RegisterWrite(server.ServerTool{Tool: RemoveTeamRepoTool, Handler: RemoveTeamRepoFn})
}
func ListOrgTeamsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListOrgTeamsFn")
org, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("org is required"))
}
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
teams, _, err := client.ListOrgTeams(org, gitea_sdk.ListTeamsOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list org %s teams err: %v", org, err))
}
return to.TextResult(teams)
}
func GetTeamFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetTeamFn")
id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
team, _, err := client.GetTeam(id)
if err != nil {
return to.ErrorResult(fmt.Errorf("get team %d err: %v", id, err))
}
return to.TextResult(team)
}
func CreateTeamFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateTeamFn")
org, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("org is required"))
}
name, ok := req.GetArguments()["name"].(string)
if !ok {
return to.ErrorResult(errors.New("name is required"))
}
opt := gitea_sdk.CreateTeamOption{
Name: name,
}
if v, ok := req.GetArguments()["description"].(string); ok {
opt.Description = v
}
if v, ok := req.GetArguments()["permission"].(string); ok {
opt.Permission = gitea_sdk.AccessMode(v)
}
if v, ok := req.GetArguments()["can_create_org_repo"].(bool); ok {
opt.CanCreateOrgRepo = v
}
if v, ok := req.GetArguments()["includes_all_repositories"].(bool); ok {
opt.IncludesAllRepositories = v
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
team, _, err := client.CreateTeam(org, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("create team %s in org %s err: %v", name, org, err))
}
return to.TextResult(team)
}
func EditTeamFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called EditTeamFn")
id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil {
return to.ErrorResult(err)
}
opt := gitea_sdk.EditTeamOption{}
if v, ok := req.GetArguments()["name"].(string); ok {
opt.Name = v
}
if v, ok := req.GetArguments()["description"].(string); ok {
opt.Description = &v
}
if v, ok := req.GetArguments()["permission"].(string); ok {
perm := gitea_sdk.AccessMode(v)
opt.Permission = perm
}
if v, ok := req.GetArguments()["can_create_org_repo"].(bool); ok {
opt.CanCreateOrgRepo = &v
}
if v, ok := req.GetArguments()["includes_all_repositories"].(bool); ok {
opt.IncludesAllRepositories = &v
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.EditTeam(id, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("edit team %d err: %v", id, err))
}
team, _, err := client.GetTeam(id)
if err != nil {
return to.ErrorResult(fmt.Errorf("get team %d after edit err: %v", id, err))
}
return to.TextResult(team)
}
func DeleteTeamFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteTeamFn")
id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.DeleteTeam(id)
if err != nil {
return to.ErrorResult(fmt.Errorf("delete team %d err: %v", id, err))
}
return to.TextResult(map[string]any{"status": "deleted", "team_id": id})
}
func ListTeamMembersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListTeamMembersFn")
id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil {
return to.ErrorResult(err)
}
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
members, _, err := client.ListTeamMembers(id, gitea_sdk.ListTeamMembersOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list team %d members err: %v", id, err))
}
return to.TextResult(members)
}
func GetTeamMemberFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetTeamMemberFn")
id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil {
return to.ErrorResult(err)
}
username, ok := req.GetArguments()["username"].(string)
if !ok {
return to.ErrorResult(errors.New("username is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
member, _, err := client.GetTeamMember(id, username)
if err != nil {
return to.ErrorResult(fmt.Errorf("get team %d member %s err: %v", id, username, err))
}
return to.TextResult(member)
}
func AddTeamMemberFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called AddTeamMemberFn")
id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil {
return to.ErrorResult(err)
}
username, ok := req.GetArguments()["username"].(string)
if !ok {
return to.ErrorResult(errors.New("username is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.AddTeamMember(id, username)
if err != nil {
return to.ErrorResult(fmt.Errorf("add member %s to team %d err: %v", username, id, err))
}
return to.TextResult(map[string]any{"status": "added", "team_id": id, "username": username})
}
func RemoveTeamMemberFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called RemoveTeamMemberFn")
id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil {
return to.ErrorResult(err)
}
username, ok := req.GetArguments()["username"].(string)
if !ok {
return to.ErrorResult(errors.New("username is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.RemoveTeamMember(id, username)
if err != nil {
return to.ErrorResult(fmt.Errorf("remove member %s from team %d err: %v", username, id, err))
}
return to.TextResult(map[string]any{"status": "removed", "team_id": id, "username": username})
}
func ListTeamReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListTeamReposFn")
id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil {
return to.ErrorResult(err)
}
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
repos, _, err := client.ListTeamRepositories(id, gitea_sdk.ListTeamRepositoriesOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list team %d repos err: %v", id, err))
}
return to.TextResult(repos)
}
func AddTeamRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called AddTeamRepoFn")
id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil {
return to.ErrorResult(err)
}
org, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("org is required"))
}
repo, ok := req.GetArguments()["repo"].(string)
if !ok {
return to.ErrorResult(errors.New("repo is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.AddTeamRepository(id, org, repo)
if err != nil {
return to.ErrorResult(fmt.Errorf("add repo %s/%s to team %d err: %v", org, repo, id, err))
}
return to.TextResult(map[string]any{"status": "added", "team_id": id, "org": org, "repo": repo})
}
func RemoveTeamRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called RemoveTeamRepoFn")
id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil {
return to.ErrorResult(err)
}
org, ok := req.GetArguments()["org"].(string)
if !ok {
return to.ErrorResult(errors.New("org is required"))
}
repo, ok := req.GetArguments()["repo"].(string)
if !ok {
return to.ErrorResult(errors.New("repo is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.RemoveTeamRepository(id, org, repo)
if err != nil {
return to.ErrorResult(fmt.Errorf("remove repo %s/%s from team %d err: %v", org, repo, id, err))
}
return to.TextResult(map[string]any{"status": "removed", "team_id": id, "org": org, "repo": repo})
}

View File

@@ -0,0 +1,242 @@
package packages
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
"git.lethalbits.com/lethalbits/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 (
ListPackagesToolName = "list_packages"
GetPackageToolName = "get_package"
DeletePackageToolName = "delete_package"
ListPackageFilesToolName = "list_package_files"
GetLatestPackageToolName = "get_latest_package"
LinkPackageToolName = "link_package"
UnlinkPackageToolName = "unlink_package"
)
var (
ListPackagesTool = mcp.NewTool(
ListPackagesToolName,
mcp.WithDescription("List packages for a user or organization"),
mcp.WithString("owner", mcp.Required(), mcp.Description("package owner (user or org)")),
mcp.WithString("type", mcp.Description("package type filter (e.g., generic, container, npm, pypi, rubygems, maven, nuget, debian, alpine, go, cargo, chef, composer, conan, conda, cran, pub, helm, rpm, swift, vagrant)")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
GetPackageTool = mcp.NewTool(
GetPackageToolName,
mcp.WithDescription("Get a specific package by type, name, and version"),
mcp.WithString("owner", mcp.Required(), mcp.Description("package owner")),
mcp.WithString("type", mcp.Required(), mcp.Description("package type (e.g., generic, container, npm)")),
mcp.WithString("name", mcp.Required(), mcp.Description("package name")),
mcp.WithString("version", mcp.Required(), mcp.Description("package version")),
)
DeletePackageTool = mcp.NewTool(
DeletePackageToolName,
mcp.WithDescription("Delete a specific package version"),
mcp.WithString("owner", mcp.Required(), mcp.Description("package owner")),
mcp.WithString("type", mcp.Required(), mcp.Description("package type")),
mcp.WithString("name", mcp.Required(), mcp.Description("package name")),
mcp.WithString("version", mcp.Required(), mcp.Description("package version")),
)
ListPackageFilesTool = mcp.NewTool(
ListPackageFilesToolName,
mcp.WithDescription("List files of a package version"),
mcp.WithString("owner", mcp.Required(), mcp.Description("package owner")),
mcp.WithString("type", mcp.Required(), mcp.Description("package type")),
mcp.WithString("name", mcp.Required(), mcp.Description("package name")),
mcp.WithString("version", mcp.Required(), mcp.Description("package version")),
)
GetLatestPackageTool = mcp.NewTool(
GetLatestPackageToolName,
mcp.WithDescription("Get the latest version of a package"),
mcp.WithString("owner", mcp.Required(), mcp.Description("package owner")),
mcp.WithString("type", mcp.Required(), mcp.Description("package type")),
mcp.WithString("name", mcp.Required(), mcp.Description("package name")),
)
LinkPackageTool = mcp.NewTool(
LinkPackageToolName,
mcp.WithDescription("Link a package to a repository"),
mcp.WithString("owner", mcp.Required(), mcp.Description("package owner")),
mcp.WithString("type", mcp.Required(), mcp.Description("package type")),
mcp.WithString("name", mcp.Required(), mcp.Description("package name")),
mcp.WithString("repo_name", mcp.Required(), mcp.Description("repository name to link to")),
)
UnlinkPackageTool = mcp.NewTool(
UnlinkPackageToolName,
mcp.WithDescription("Unlink a package from its repository"),
mcp.WithString("owner", mcp.Required(), mcp.Description("package owner")),
mcp.WithString("type", mcp.Required(), mcp.Description("package type")),
mcp.WithString("name", mcp.Required(), mcp.Description("package name")),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: ListPackagesTool, Handler: ListPackagesFn})
Tool.RegisterRead(server.ServerTool{Tool: GetPackageTool, Handler: GetPackageFn})
Tool.RegisterRead(server.ServerTool{Tool: ListPackageFilesTool, Handler: ListPackageFilesFn})
Tool.RegisterRead(server.ServerTool{Tool: GetLatestPackageTool, Handler: GetLatestPackageFn})
Tool.RegisterWrite(server.ServerTool{Tool: DeletePackageTool, Handler: DeletePackageFn})
Tool.RegisterWrite(server.ServerTool{Tool: LinkPackageTool, Handler: LinkPackageFn})
Tool.RegisterWrite(server.ServerTool{Tool: UnlinkPackageTool, Handler: UnlinkPackageFn})
}
func ListPackagesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListPackagesFn")
owner, _ := req.GetArguments()["owner"].(string)
if owner == "" {
return to.ErrorResult(errors.New("owner is required"))
}
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
opt := gitea_sdk.ListPackagesOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
packages, _, err := client.ListPackages(owner, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("list packages err: %v", err))
}
return to.TextResult(packages)
}
func GetPackageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetPackageFn")
owner, _ := req.GetArguments()["owner"].(string)
pkgType, _ := req.GetArguments()["type"].(string)
name, _ := req.GetArguments()["name"].(string)
version, _ := req.GetArguments()["version"].(string)
if owner == "" || pkgType == "" || name == "" || version == "" {
return to.ErrorResult(errors.New("owner, type, name, and version are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
pkg, _, err := client.GetPackage(owner, pkgType, name, version)
if err != nil {
return to.ErrorResult(fmt.Errorf("get package err: %v", err))
}
return to.TextResult(pkg)
}
func DeletePackageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeletePackageFn")
owner, _ := req.GetArguments()["owner"].(string)
pkgType, _ := req.GetArguments()["type"].(string)
name, _ := req.GetArguments()["name"].(string)
version, _ := req.GetArguments()["version"].(string)
if owner == "" || pkgType == "" || name == "" || version == "" {
return to.ErrorResult(errors.New("owner, type, name, and version are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.DeletePackage(owner, pkgType, name, version)
if err != nil {
return to.ErrorResult(fmt.Errorf("delete package err: %v", err))
}
return to.TextResult(map[string]string{"status": "deleted"})
}
func ListPackageFilesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListPackageFilesFn")
owner, _ := req.GetArguments()["owner"].(string)
pkgType, _ := req.GetArguments()["type"].(string)
name, _ := req.GetArguments()["name"].(string)
version, _ := req.GetArguments()["version"].(string)
if owner == "" || pkgType == "" || name == "" || version == "" {
return to.ErrorResult(errors.New("owner, type, name, and version are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
files, _, err := client.ListPackageFiles(owner, pkgType, name, version)
if err != nil {
return to.ErrorResult(fmt.Errorf("list package files err: %v", err))
}
return to.TextResult(files)
}
func GetLatestPackageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetLatestPackageFn")
owner, _ := req.GetArguments()["owner"].(string)
pkgType, _ := req.GetArguments()["type"].(string)
name, _ := req.GetArguments()["name"].(string)
if owner == "" || pkgType == "" || name == "" {
return to.ErrorResult(errors.New("owner, type, and name are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
pkg, _, err := client.GetLatestPackage(owner, pkgType, name)
if err != nil {
return to.ErrorResult(fmt.Errorf("get latest package err: %v", err))
}
return to.TextResult(pkg)
}
func LinkPackageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called LinkPackageFn")
owner, _ := req.GetArguments()["owner"].(string)
pkgType, _ := req.GetArguments()["type"].(string)
name, _ := req.GetArguments()["name"].(string)
repoName, _ := req.GetArguments()["repo_name"].(string)
if owner == "" || pkgType == "" || name == "" || repoName == "" {
return to.ErrorResult(errors.New("owner, type, name, and repo_name are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.LinkPackage(owner, pkgType, name, repoName)
if err != nil {
return to.ErrorResult(fmt.Errorf("link package err: %v", err))
}
return to.TextResult(map[string]string{"status": "linked", "repo": repoName})
}
func UnlinkPackageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called UnlinkPackageFn")
owner, _ := req.GetArguments()["owner"].(string)
pkgType, _ := req.GetArguments()["type"].(string)
name, _ := req.GetArguments()["name"].(string)
if owner == "" || pkgType == "" || name == "" {
return to.ErrorResult(errors.New("owner, type, and name are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.UnlinkPackage(owner, pkgType, name)
if err != nil {
return to.ErrorResult(fmt.Errorf("unlink package err: %v", err))
}
return to.TextResult(map[string]string{"status": "unlinked"})
}

View File

@@ -5,11 +5,11 @@ import (
"errors" "errors"
"fmt" "fmt"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
"gitea.com/gitea/gitea-mcp/pkg/tool" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/tool"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"

View File

@@ -9,7 +9,7 @@ import (
"sync" "sync"
"testing" "testing"
"gitea.com/gitea/gitea-mcp/pkg/flag" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/flag"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
) )

View File

@@ -5,9 +5,9 @@ import (
"errors" "errors"
"fmt" "fmt"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/to" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"

View File

@@ -0,0 +1,226 @@
package repo
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
ListBranchProtectionsToolName = "list_branch_protections"
GetBranchProtectionToolName = "get_branch_protection"
CreateBranchProtectionToolName = "create_branch_protection"
EditBranchProtectionToolName = "edit_branch_protection"
DeleteBranchProtectionToolName = "delete_branch_protection"
)
var (
ListBranchProtectionsTool = mcp.NewTool(
ListBranchProtectionsToolName,
mcp.WithDescription("List branch protections for a 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)),
)
GetBranchProtectionTool = mcp.NewTool(
GetBranchProtectionToolName,
mcp.WithDescription("Get a branch protection rule 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("branch protection rule name")),
)
CreateBranchProtectionTool = mcp.NewTool(
CreateBranchProtectionToolName,
mcp.WithDescription("Create a branch protection rule"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("rule_name", mcp.Required(), mcp.Description("rule name (glob pattern for branch matching)")),
mcp.WithBoolean("enable_push", mcp.Description("enable push to protected branch")),
mcp.WithBoolean("enable_merge_whitelist", mcp.Description("enable merge whitelist")),
mcp.WithBoolean("enable_status_check", mcp.Description("enable status check")),
mcp.WithNumber("required_approvals", mcp.Description("number of required approvals")),
mcp.WithBoolean("block_on_rejected_reviews", mcp.Description("block merge on rejected reviews")),
mcp.WithBoolean("dismiss_stale_approvals", mcp.Description("dismiss stale approvals on new commits")),
)
EditBranchProtectionTool = mcp.NewTool(
EditBranchProtectionToolName,
mcp.WithDescription("Edit a branch protection rule"),
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("branch protection rule name")),
mcp.WithBoolean("enable_push", mcp.Description("enable push to protected branch")),
mcp.WithBoolean("enable_merge_whitelist", mcp.Description("enable merge whitelist")),
mcp.WithBoolean("enable_status_check", mcp.Description("enable status check")),
mcp.WithNumber("required_approvals", mcp.Description("number of required approvals")),
mcp.WithBoolean("block_on_rejected_reviews", mcp.Description("block merge on rejected reviews")),
mcp.WithBoolean("dismiss_stale_approvals", mcp.Description("dismiss stale approvals on new commits")),
)
DeleteBranchProtectionTool = mcp.NewTool(
DeleteBranchProtectionToolName,
mcp.WithDescription("Delete a branch protection rule"),
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("branch protection rule name")),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: ListBranchProtectionsTool, Handler: ListBranchProtectionsFn})
Tool.RegisterRead(server.ServerTool{Tool: GetBranchProtectionTool, Handler: GetBranchProtectionFn})
Tool.RegisterWrite(server.ServerTool{Tool: CreateBranchProtectionTool, Handler: CreateBranchProtectionFn})
Tool.RegisterWrite(server.ServerTool{Tool: EditBranchProtectionTool, Handler: EditBranchProtectionFn})
Tool.RegisterWrite(server.ServerTool{Tool: DeleteBranchProtectionTool, Handler: DeleteBranchProtectionFn})
}
func ListBranchProtectionsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListBranchProtectionsFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
bps, _, err := client.ListBranchProtections(owner, repo, gitea_sdk.ListBranchProtectionsOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list branch protections err: %v", err))
}
return to.TextResult(bps)
}
func GetBranchProtectionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetBranchProtectionFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
name, _ := req.GetArguments()["name"].(string)
if owner == "" || repo == "" || name == "" {
return to.ErrorResult(errors.New("owner, repo, and name are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
bp, _, err := client.GetBranchProtection(owner, repo, name)
if err != nil {
return to.ErrorResult(fmt.Errorf("get branch protection err: %v", err))
}
return to.TextResult(bp)
}
func CreateBranchProtectionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateBranchProtectionFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
ruleName, _ := req.GetArguments()["rule_name"].(string)
if owner == "" || repo == "" || ruleName == "" {
return to.ErrorResult(errors.New("owner, repo, and rule_name are required"))
}
opt := gitea_sdk.CreateBranchProtectionOption{
RuleName: ruleName,
}
if v, ok := req.GetArguments()["enable_push"].(bool); ok {
opt.EnablePush = v
}
if v, ok := req.GetArguments()["enable_merge_whitelist"].(bool); ok {
opt.EnableMergeWhitelist = v
}
if v, ok := req.GetArguments()["enable_status_check"].(bool); ok {
opt.EnableStatusCheck = v
}
if v, ok := req.GetArguments()["required_approvals"].(float64); ok {
opt.RequiredApprovals = int64(v)
}
if v, ok := req.GetArguments()["block_on_rejected_reviews"].(bool); ok {
opt.BlockOnRejectedReviews = v
}
if v, ok := req.GetArguments()["dismiss_stale_approvals"].(bool); ok {
opt.DismissStaleApprovals = v
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
bp, _, err := client.CreateBranchProtection(owner, repo, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("create branch protection err: %v", err))
}
return to.TextResult(bp)
}
func EditBranchProtectionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called EditBranchProtectionFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
name, _ := req.GetArguments()["name"].(string)
if owner == "" || repo == "" || name == "" {
return to.ErrorResult(errors.New("owner, repo, and name are required"))
}
opt := gitea_sdk.EditBranchProtectionOption{}
if v, ok := req.GetArguments()["enable_push"].(bool); ok {
opt.EnablePush = &v
}
if v, ok := req.GetArguments()["enable_merge_whitelist"].(bool); ok {
opt.EnableMergeWhitelist = &v
}
if v, ok := req.GetArguments()["enable_status_check"].(bool); ok {
opt.EnableStatusCheck = &v
}
if v, ok := req.GetArguments()["required_approvals"].(float64); ok {
approvals := int64(v)
opt.RequiredApprovals = &approvals
}
if v, ok := req.GetArguments()["block_on_rejected_reviews"].(bool); ok {
opt.BlockOnRejectedReviews = &v
}
if v, ok := req.GetArguments()["dismiss_stale_approvals"].(bool); ok {
opt.DismissStaleApprovals = &v
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
bp, _, err := client.EditBranchProtection(owner, repo, name, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("edit branch protection err: %v", err))
}
return to.TextResult(bp)
}
func DeleteBranchProtectionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteBranchProtectionFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
name, _ := req.GetArguments()["name"].(string)
if owner == "" || repo == "" || name == "" {
return to.ErrorResult(errors.New("owner, repo, and name are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.DeleteBranchProtection(owner, repo, name)
if err != nil {
return to.ErrorResult(fmt.Errorf("delete branch protection err: %v", err))
}
return to.TextResult(map[string]string{"status": "deleted"})
}

View File

@@ -0,0 +1,233 @@
package repo
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
ListCollaboratorsToolName = "list_collaborators"
IsCollaboratorToolName = "is_collaborator"
CollaboratorPermissionToolName = "collaborator_permission"
AddCollaboratorToolName = "add_collaborator"
DeleteCollaboratorToolName = "delete_collaborator"
GetReviewersToolName = "get_reviewers"
GetAssigneesToolName = "get_assignees"
)
var (
ListCollaboratorsTool = mcp.NewTool(
ListCollaboratorsToolName,
mcp.WithDescription("List a repository's collaborators"),
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)),
)
IsCollaboratorTool = mcp.NewTool(
IsCollaboratorToolName,
mcp.WithDescription("Check if a user is a collaborator of a repository"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("collaborator", mcp.Required(), mcp.Description("username to check")),
)
CollaboratorPermissionTool = mcp.NewTool(
CollaboratorPermissionToolName,
mcp.WithDescription("Get a collaborator's permission level on a repository"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("collaborator", mcp.Required(), mcp.Description("collaborator username")),
)
AddCollaboratorTool = mcp.NewTool(
AddCollaboratorToolName,
mcp.WithDescription("Add a collaborator to a repository"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("collaborator", mcp.Required(), mcp.Description("username to add")),
mcp.WithString("permission", mcp.Description("permission level: read, write, admin (default: write)")),
)
DeleteCollaboratorTool = mcp.NewTool(
DeleteCollaboratorToolName,
mcp.WithDescription("Remove a collaborator from a repository"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("collaborator", mcp.Required(), mcp.Description("username to remove")),
)
GetReviewersTool = mcp.NewTool(
GetReviewersToolName,
mcp.WithDescription("Get the list of users who can review PRs in a repository"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
)
GetAssigneesTool = mcp.NewTool(
GetAssigneesToolName,
mcp.WithDescription("Get the list of users who can be assigned to issues in a repository"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: ListCollaboratorsTool, Handler: ListCollaboratorsFn})
Tool.RegisterRead(server.ServerTool{Tool: IsCollaboratorTool, Handler: IsCollaboratorFn})
Tool.RegisterRead(server.ServerTool{Tool: CollaboratorPermissionTool, Handler: CollaboratorPermissionFn})
Tool.RegisterRead(server.ServerTool{Tool: GetReviewersTool, Handler: GetReviewersFn})
Tool.RegisterRead(server.ServerTool{Tool: GetAssigneesTool, Handler: GetAssigneesFn})
Tool.RegisterWrite(server.ServerTool{Tool: AddCollaboratorTool, Handler: AddCollaboratorFn})
Tool.RegisterWrite(server.ServerTool{Tool: DeleteCollaboratorTool, Handler: DeleteCollaboratorFn})
}
func ListCollaboratorsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListCollaboratorsFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
users, _, err := client.ListCollaborators(owner, repo, gitea_sdk.ListCollaboratorsOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list collaborators err: %v", err))
}
return to.TextResult(users)
}
func IsCollaboratorFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called IsCollaboratorFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
collaborator, _ := req.GetArguments()["collaborator"].(string)
if owner == "" || repo == "" || collaborator == "" {
return to.ErrorResult(errors.New("owner, repo, and collaborator are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
isCollab, _, err := client.IsCollaborator(owner, repo, collaborator)
if err != nil {
return to.ErrorResult(fmt.Errorf("check collaborator err: %v", err))
}
return to.TextResult(map[string]any{"collaborator": collaborator, "is_collaborator": isCollab})
}
func CollaboratorPermissionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CollaboratorPermissionFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
collaborator, _ := req.GetArguments()["collaborator"].(string)
if owner == "" || repo == "" || collaborator == "" {
return to.ErrorResult(errors.New("owner, repo, and collaborator are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
perm, _, err := client.CollaboratorPermission(owner, repo, collaborator)
if err != nil {
return to.ErrorResult(fmt.Errorf("get collaborator permission err: %v", err))
}
return to.TextResult(perm)
}
func AddCollaboratorFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called AddCollaboratorFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
collaborator, _ := req.GetArguments()["collaborator"].(string)
if owner == "" || repo == "" || collaborator == "" {
return to.ErrorResult(errors.New("owner, repo, and collaborator are required"))
}
opt := gitea_sdk.AddCollaboratorOption{}
if v, ok := req.GetArguments()["permission"].(string); ok {
perm := gitea_sdk.AccessMode(v)
opt.Permission = &perm
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.AddCollaborator(owner, repo, collaborator, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("add collaborator err: %v", err))
}
return to.TextResult(map[string]string{"status": "added", "collaborator": collaborator})
}
func DeleteCollaboratorFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteCollaboratorFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
collaborator, _ := req.GetArguments()["collaborator"].(string)
if owner == "" || repo == "" || collaborator == "" {
return to.ErrorResult(errors.New("owner, repo, and collaborator are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.DeleteCollaborator(owner, repo, collaborator)
if err != nil {
return to.ErrorResult(fmt.Errorf("delete collaborator err: %v", err))
}
return to.TextResult(map[string]string{"status": "removed", "collaborator": collaborator})
}
func GetReviewersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetReviewersFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
users, _, err := client.GetReviewers(owner, repo)
if err != nil {
return to.ErrorResult(fmt.Errorf("get reviewers err: %v", err))
}
return to.TextResult(users)
}
func GetAssigneesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetAssigneesFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
users, _, err := client.GetAssignees(owner, repo)
if err != nil {
return to.ErrorResult(fmt.Errorf("get assignees err: %v", err))
}
return to.TextResult(users)
}

View File

@@ -5,10 +5,10 @@ import (
"errors" "errors"
"fmt" "fmt"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"

View File

@@ -0,0 +1,161 @@
package repo
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
ListDeployKeysToolName = "list_deploy_keys"
GetDeployKeyToolName = "get_deploy_key"
CreateDeployKeyToolName = "create_deploy_key"
DeleteDeployKeyToolName = "delete_deploy_key"
)
var (
ListDeployKeysTool = mcp.NewTool(
ListDeployKeysToolName,
mcp.WithDescription("List a repository's deploy keys"),
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)),
)
GetDeployKeyTool = mcp.NewTool(
GetDeployKeyToolName,
mcp.WithDescription("Get a deploy key by ID"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithNumber("key_id", mcp.Required(), mcp.Description("deploy key ID")),
)
CreateDeployKeyTool = mcp.NewTool(
CreateDeployKeyToolName,
mcp.WithDescription("Add a deploy key to a repository"),
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("key title")),
mcp.WithString("key", mcp.Required(), mcp.Description("public key content")),
mcp.WithBoolean("read_only", mcp.Description("whether the key has read-only access (default: true)")),
)
DeleteDeployKeyTool = mcp.NewTool(
DeleteDeployKeyToolName,
mcp.WithDescription("Remove a deploy key from a repository"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithNumber("key_id", mcp.Required(), mcp.Description("deploy key ID")),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: ListDeployKeysTool, Handler: ListDeployKeysFn})
Tool.RegisterRead(server.ServerTool{Tool: GetDeployKeyTool, Handler: GetDeployKeyFn})
Tool.RegisterWrite(server.ServerTool{Tool: CreateDeployKeyTool, Handler: CreateDeployKeyFn})
Tool.RegisterWrite(server.ServerTool{Tool: DeleteDeployKeyTool, Handler: DeleteDeployKeyFn})
}
func ListDeployKeysFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListDeployKeysFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
keys, _, err := client.ListDeployKeys(owner, repo, gitea_sdk.ListDeployKeysOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list deploy keys err: %v", err))
}
return to.TextResult(keys)
}
func GetDeployKeyFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetDeployKeyFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
keyID, err := params.GetIndex(req.GetArguments(), "key_id")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
key, _, err := client.GetDeployKey(owner, repo, keyID)
if err != nil {
return to.ErrorResult(fmt.Errorf("get deploy key err: %v", err))
}
return to.TextResult(key)
}
func CreateDeployKeyFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateDeployKeyFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
title, _ := req.GetArguments()["title"].(string)
key, _ := req.GetArguments()["key"].(string)
if owner == "" || repo == "" || title == "" || key == "" {
return to.ErrorResult(errors.New("owner, repo, title, and key are required"))
}
opt := gitea_sdk.CreateKeyOption{
Title: title,
Key: key,
ReadOnly: true,
}
if v, ok := req.GetArguments()["read_only"].(bool); ok {
opt.ReadOnly = v
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
dk, _, err := client.CreateDeployKey(owner, repo, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("create deploy key err: %v", err))
}
return to.TextResult(dk)
}
func DeleteDeployKeyFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteDeployKeyFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
keyID, err := params.GetIndex(req.GetArguments(), "key_id")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.DeleteDeployKey(owner, repo, keyID)
if err != nil {
return to.ErrorResult(fmt.Errorf("delete deploy key err: %v", err))
}
return to.TextResult(map[string]string{"status": "deleted"})
}

View File

@@ -9,9 +9,9 @@ import (
"errors" "errors"
"fmt" "fmt"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/to" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"

175
operation/repo/git.go Normal file
View File

@@ -0,0 +1,175 @@
package repo
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
GetRepoRefToolName = "get_repo_ref"
ListAllGitRefsToolName = "list_all_git_refs"
GetTreeToolName = "get_tree"
GetRepoNoteToolName = "get_repo_note"
CompareCommitsToolName = "compare_commits"
)
var (
GetRepoRefTool = mcp.NewTool(
GetRepoRefToolName,
mcp.WithDescription("Get a git reference from a repository"),
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("git reference (e.g., refs/heads/main, refs/tags/v1.0)")),
)
ListAllGitRefsTool = mcp.NewTool(
ListAllGitRefsToolName,
mcp.WithDescription("List all git references in a repository"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
)
GetTreeTool = mcp.NewTool(
GetTreeToolName,
mcp.WithDescription("Get the tree of a repository at a given SHA"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("sha", mcp.Required(), mcp.Description("SHA of the tree")),
mcp.WithBoolean("recursive", mcp.Description("show all items recursively")),
)
GetRepoNoteTool = mcp.NewTool(
GetRepoNoteToolName,
mcp.WithDescription("Get a git note for a commit"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("sha", mcp.Required(), mcp.Description("commit SHA")),
)
CompareCommitsTool = mcp.NewTool(
CompareCommitsToolName,
mcp.WithDescription("Compare two commits in a repository"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("base", mcp.Required(), mcp.Description("base commit SHA or branch")),
mcp.WithString("head", mcp.Required(), mcp.Description("head commit SHA or branch")),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: GetRepoRefTool, Handler: GetRepoRefFn})
Tool.RegisterRead(server.ServerTool{Tool: ListAllGitRefsTool, Handler: ListAllGitRefsFn})
Tool.RegisterRead(server.ServerTool{Tool: GetTreeTool, Handler: GetTreeFn})
Tool.RegisterRead(server.ServerTool{Tool: GetRepoNoteTool, Handler: GetRepoNoteFn})
Tool.RegisterRead(server.ServerTool{Tool: CompareCommitsTool, Handler: CompareCommitsFn})
}
func GetRepoRefFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetRepoRefFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
ref, _ := req.GetArguments()["ref"].(string)
if owner == "" || repo == "" || ref == "" {
return to.ErrorResult(errors.New("owner, repo, and ref are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
reference, _, err := client.GetRepoRef(owner, repo, ref)
if err != nil {
return to.ErrorResult(fmt.Errorf("get repo ref err: %v", err))
}
return to.TextResult(reference)
}
func ListAllGitRefsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListAllGitRefsFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
refs, _, err := client.ListAllGitRefs(owner, repo)
if err != nil {
return to.ErrorResult(fmt.Errorf("list git refs err: %v", err))
}
return to.TextResult(refs)
}
func GetTreeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetTreeFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
sha, _ := req.GetArguments()["sha"].(string)
if owner == "" || repo == "" || sha == "" {
return to.ErrorResult(errors.New("owner, repo, and sha are required"))
}
opt := gitea_sdk.ListTreeOptions{
Ref: sha,
}
if v, ok := req.GetArguments()["recursive"].(bool); ok && v {
opt.Recursive = true
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
tree, _, err := client.GetTrees(owner, repo, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("get tree err: %v", err))
}
return to.TextResult(tree)
}
func GetRepoNoteFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetRepoNoteFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
sha, _ := req.GetArguments()["sha"].(string)
if owner == "" || repo == "" || sha == "" {
return to.ErrorResult(errors.New("owner, repo, and sha are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
note, _, err := client.GetRepoNote(owner, repo, sha, gitea_sdk.GetRepoNoteOptions{})
if err != nil {
return to.ErrorResult(fmt.Errorf("get repo note err: %v", err))
}
return to.TextResult(note)
}
func CompareCommitsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CompareCommitsFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
base, _ := req.GetArguments()["base"].(string)
head, _ := req.GetArguments()["head"].(string)
if owner == "" || repo == "" || base == "" || head == "" {
return to.ErrorResult(errors.New("owner, repo, base, and head are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
compare, _, err := client.CompareCommits(owner, repo, base, head)
if err != nil {
return to.ErrorResult(fmt.Errorf("compare commits err: %v", err))
}
return to.TextResult(compare)
}

227
operation/repo/hook.go Normal file
View File

@@ -0,0 +1,227 @@
package repo
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
ListRepoHooksToolName = "list_repo_hooks"
GetRepoHookToolName = "get_repo_hook"
CreateRepoHookToolName = "create_repo_hook"
EditRepoHookToolName = "edit_repo_hook"
DeleteRepoHookToolName = "delete_repo_hook"
)
var (
ListRepoHooksTool = mcp.NewTool(
ListRepoHooksToolName,
mcp.WithDescription("List a repository's webhooks"),
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)),
)
GetRepoHookTool = mcp.NewTool(
GetRepoHookToolName,
mcp.WithDescription("Get a repository webhook 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("webhook ID")),
)
CreateRepoHookTool = mcp.NewTool(
CreateRepoHookToolName,
mcp.WithDescription("Create a webhook for a repository"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("type", mcp.Required(), mcp.Description("hook type: gitea, gogs, slack, discord, dingtalk, telegram, msteams, feishu, wechatwork, packagist")),
mcp.WithString("url", mcp.Required(), mcp.Description("target URL for the webhook")),
mcp.WithString("content_type", mcp.Description("content type: json or form (default: json)")),
mcp.WithString("secret", mcp.Description("webhook secret")),
mcp.WithBoolean("active", mcp.Description("whether the webhook is active (default: true)")),
)
EditRepoHookTool = mcp.NewTool(
EditRepoHookToolName,
mcp.WithDescription("Edit a repository webhook"),
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("webhook ID")),
mcp.WithString("url", mcp.Description("target URL")),
mcp.WithString("content_type", mcp.Description("content type: json or form")),
mcp.WithString("secret", mcp.Description("webhook secret")),
mcp.WithBoolean("active", mcp.Description("whether the webhook is active")),
)
DeleteRepoHookTool = mcp.NewTool(
DeleteRepoHookToolName,
mcp.WithDescription("Delete a repository webhook"),
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("webhook ID")),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: ListRepoHooksTool, Handler: ListRepoHooksFn})
Tool.RegisterRead(server.ServerTool{Tool: GetRepoHookTool, Handler: GetRepoHookFn})
Tool.RegisterWrite(server.ServerTool{Tool: CreateRepoHookTool, Handler: CreateRepoHookFn})
Tool.RegisterWrite(server.ServerTool{Tool: EditRepoHookTool, Handler: EditRepoHookFn})
Tool.RegisterWrite(server.ServerTool{Tool: DeleteRepoHookTool, Handler: DeleteRepoHookFn})
}
func ListRepoHooksFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListRepoHooksFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
hooks, _, err := client.ListRepoHooks(owner, repo, gitea_sdk.ListHooksOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list repo hooks err: %v", err))
}
return to.TextResult(hooks)
}
func GetRepoHookFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetRepoHookFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
hook, _, err := client.GetRepoHook(owner, repo, id)
if err != nil {
return to.ErrorResult(fmt.Errorf("get repo hook err: %v", err))
}
return to.TextResult(hook)
}
func CreateRepoHookFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateRepoHookFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
hookType, _ := req.GetArguments()["type"].(string)
url, _ := req.GetArguments()["url"].(string)
if owner == "" || repo == "" || hookType == "" || url == "" {
return to.ErrorResult(errors.New("owner, repo, type, and url are required"))
}
contentType := "json"
if v, ok := req.GetArguments()["content_type"].(string); ok {
contentType = v
}
config := map[string]string{
"url": url,
"content_type": contentType,
}
if v, ok := req.GetArguments()["secret"].(string); ok {
config["secret"] = v
}
opt := gitea_sdk.CreateHookOption{
Type: gitea_sdk.HookType(hookType),
Config: config,
Active: true,
}
if v, ok := req.GetArguments()["active"].(bool); ok {
opt.Active = v
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
hook, _, err := client.CreateRepoHook(owner, repo, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("create repo hook err: %v", err))
}
return to.TextResult(hook)
}
func EditRepoHookFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called EditRepoHookFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil {
return to.ErrorResult(err)
}
opt := gitea_sdk.EditHookOption{}
config := map[string]string{}
if v, ok := req.GetArguments()["url"].(string); ok {
config["url"] = v
}
if v, ok := req.GetArguments()["content_type"].(string); ok {
config["content_type"] = v
}
if v, ok := req.GetArguments()["secret"].(string); ok {
config["secret"] = v
}
if len(config) > 0 {
opt.Config = config
}
if v, ok := req.GetArguments()["active"].(bool); ok {
opt.Active = &v
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.EditRepoHook(owner, repo, id, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("edit repo hook err: %v", err))
}
return to.TextResult(map[string]string{"status": "updated"})
}
func DeleteRepoHookFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteRepoHookFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.DeleteRepoHook(owner, repo, id)
if err != nil {
return to.ErrorResult(fmt.Errorf("delete repo hook err: %v", err))
}
return to.TextResult(map[string]string{"status": "deleted"})
}

156
operation/repo/mirror.go Normal file
View File

@@ -0,0 +1,156 @@
package repo
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
CreatePushMirrorToolName = "create_push_mirror"
ListPushMirrorsToolName = "list_push_mirrors"
GetPushMirrorByRemoteNameToolName = "get_push_mirror"
DeletePushMirrorToolName = "delete_push_mirror"
)
var (
CreatePushMirrorTool = mcp.NewTool(
CreatePushMirrorToolName,
mcp.WithDescription("Create a push mirror for a repository"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("remote_address", mcp.Required(), mcp.Description("remote git URL to push to")),
mcp.WithString("remote_username", mcp.Description("remote username for authentication")),
mcp.WithString("remote_password", mcp.Description("remote password/token for authentication")),
mcp.WithString("interval", mcp.Description("sync interval (e.g., 8h0m0s)")),
mcp.WithBoolean("sync_on_commit", mcp.Description("sync on commit")),
)
ListPushMirrorsTool = mcp.NewTool(
ListPushMirrorsToolName,
mcp.WithDescription("List push mirrors for a repository"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
)
GetPushMirrorByRemoteNameTool = mcp.NewTool(
GetPushMirrorByRemoteNameToolName,
mcp.WithDescription("Get a push mirror by remote name"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("remote_name", mcp.Required(), mcp.Description("remote name")),
)
DeletePushMirrorTool = mcp.NewTool(
DeletePushMirrorToolName,
mcp.WithDescription("Delete a push mirror by remote name"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("remote_name", mcp.Required(), mcp.Description("remote name to delete")),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: ListPushMirrorsTool, Handler: ListPushMirrorsFn})
Tool.RegisterRead(server.ServerTool{Tool: GetPushMirrorByRemoteNameTool, Handler: GetPushMirrorByRemoteNameFn})
Tool.RegisterWrite(server.ServerTool{Tool: CreatePushMirrorTool, Handler: CreatePushMirrorFn})
Tool.RegisterWrite(server.ServerTool{Tool: DeletePushMirrorTool, Handler: DeletePushMirrorFn})
}
func CreatePushMirrorFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreatePushMirrorFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
remoteAddr, _ := req.GetArguments()["remote_address"].(string)
if owner == "" || repo == "" || remoteAddr == "" {
return to.ErrorResult(errors.New("owner, repo, and remote_address are required"))
}
opt := gitea_sdk.CreatePushMirrorOption{
RemoteAddress: remoteAddr,
}
if v, ok := req.GetArguments()["remote_username"].(string); ok {
opt.RemoteUsername = v
}
if v, ok := req.GetArguments()["remote_password"].(string); ok {
opt.RemotePassword = v
}
if v, ok := req.GetArguments()["interval"].(string); ok {
opt.Interval = v
}
if v, ok := req.GetArguments()["sync_on_commit"].(bool); ok {
opt.SyncONCommit = v
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
mirror, _, err := client.PushMirrors(owner, repo, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("create push mirror err: %v", err))
}
return to.TextResult(mirror)
}
func ListPushMirrorsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListPushMirrorsFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
mirrors, _, err := client.ListPushMirrors(owner, repo, gitea_sdk.ListOptions{})
if err != nil {
return to.ErrorResult(fmt.Errorf("list push mirrors err: %v", err))
}
return to.TextResult(mirrors)
}
func GetPushMirrorByRemoteNameFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetPushMirrorByRemoteNameFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
remoteName, _ := req.GetArguments()["remote_name"].(string)
if owner == "" || repo == "" || remoteName == "" {
return to.ErrorResult(errors.New("owner, repo, and remote_name are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
mirror, _, err := client.GetPushMirrorByRemoteName(owner, repo, remoteName)
if err != nil {
return to.ErrorResult(fmt.Errorf("get push mirror err: %v", err))
}
return to.TextResult(mirror)
}
func DeletePushMirrorFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeletePushMirrorFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
remoteName, _ := req.GetArguments()["remote_name"].(string)
if owner == "" || repo == "" || remoteName == "" {
return to.ErrorResult(errors.New("owner, repo, and remote_name are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.DeletePushMirror(owner, repo, remoteName)
if err != nil {
return to.ErrorResult(fmt.Errorf("delete push mirror err: %v", err))
}
return to.TextResult(map[string]string{"status": "deleted"})
}

View File

@@ -6,10 +6,10 @@ import (
"fmt" "fmt"
"time" "time"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"

View File

@@ -5,11 +5,11 @@ import (
"errors" "errors"
"fmt" "fmt"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
"gitea.com/gitea/gitea-mcp/pkg/tool" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/tool"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"

View File

@@ -0,0 +1,216 @@
package repo
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
ListStargazersToolName = "list_stargazers"
ListForksToolName = "list_forks"
GetWatchedReposToolName = "get_watched_repos"
GetMyWatchedReposToolName = "get_my_watched_repos"
CheckRepoWatchToolName = "check_repo_watch"
WatchRepoToolName = "watch_repo"
UnWatchRepoToolName = "unwatch_repo"
)
var (
ListStargazersTool = mcp.NewTool(
ListStargazersToolName,
mcp.WithDescription("List a repository's stargazers"),
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)),
)
ListForksTool = mcp.NewTool(
ListForksToolName,
mcp.WithDescription("List a repository's forks"),
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)),
)
GetWatchedReposTool = mcp.NewTool(
GetWatchedReposToolName,
mcp.WithDescription("List repositories watched by a user"),
mcp.WithString("username", mcp.Required(), mcp.Description("username")),
)
GetMyWatchedReposTool = mcp.NewTool(
GetMyWatchedReposToolName,
mcp.WithDescription("List repositories watched by the authenticated user"),
)
CheckRepoWatchTool = mcp.NewTool(
CheckRepoWatchToolName,
mcp.WithDescription("Check if the authenticated user is watching a repository"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
)
WatchRepoTool = mcp.NewTool(
WatchRepoToolName,
mcp.WithDescription("Watch a repository"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
)
UnWatchRepoTool = mcp.NewTool(
UnWatchRepoToolName,
mcp.WithDescription("Unwatch a repository"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: ListStargazersTool, Handler: ListStargazersFn})
Tool.RegisterRead(server.ServerTool{Tool: ListForksTool, Handler: ListForksFn})
Tool.RegisterRead(server.ServerTool{Tool: GetWatchedReposTool, Handler: GetWatchedReposFn})
Tool.RegisterRead(server.ServerTool{Tool: GetMyWatchedReposTool, Handler: GetMyWatchedReposFn})
Tool.RegisterRead(server.ServerTool{Tool: CheckRepoWatchTool, Handler: CheckRepoWatchFn})
Tool.RegisterWrite(server.ServerTool{Tool: WatchRepoTool, Handler: WatchRepoFn})
Tool.RegisterWrite(server.ServerTool{Tool: UnWatchRepoTool, Handler: UnWatchRepoFn})
}
func ListStargazersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListStargazersFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
users, _, err := client.ListRepoStargazers(owner, repo, gitea_sdk.ListStargazersOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list stargazers err: %v", err))
}
return to.TextResult(users)
}
func ListForksFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListForksFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
repos, _, err := client.ListForks(owner, repo, gitea_sdk.ListForksOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list forks err: %v", err))
}
return to.TextResult(repos)
}
func GetWatchedReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetWatchedReposFn")
username, _ := req.GetArguments()["username"].(string)
if username == "" {
return to.ErrorResult(errors.New("username is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
repos, _, err := client.GetWatchedRepos(username)
if err != nil {
return to.ErrorResult(fmt.Errorf("get watched repos err: %v", err))
}
return to.TextResult(repos)
}
func GetMyWatchedReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetMyWatchedReposFn")
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
repos, _, err := client.GetMyWatchedRepos()
if err != nil {
return to.ErrorResult(fmt.Errorf("get my watched repos err: %v", err))
}
return to.TextResult(repos)
}
func CheckRepoWatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CheckRepoWatchFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
watching, _, err := client.CheckRepoWatch(owner, repo)
if err != nil {
return to.ErrorResult(fmt.Errorf("check repo watch err: %v", err))
}
return to.TextResult(map[string]any{"watching": watching})
}
func WatchRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called WatchRepoFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.WatchRepo(owner, repo)
if err != nil {
return to.ErrorResult(fmt.Errorf("watch repo err: %v", err))
}
return to.TextResult(map[string]string{"status": "watching"})
}
func UnWatchRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called UnWatchRepoFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.UnWatchRepo(owner, repo)
if err != nil {
return to.ErrorResult(fmt.Errorf("unwatch repo err: %v", err))
}
return to.TextResult(map[string]string{"status": "unwatched"})
}

134
operation/repo/status.go Normal file
View File

@@ -0,0 +1,134 @@
package repo
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
CreateStatusToolName = "create_commit_status"
ListStatusesToolName = "list_commit_statuses"
GetCombinedStatusToolName = "get_combined_status"
)
var (
CreateStatusTool = mcp.NewTool(
CreateStatusToolName,
mcp.WithDescription("Create a commit status"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("sha", mcp.Required(), mcp.Description("commit SHA")),
mcp.WithString("state", mcp.Required(), mcp.Description("status state: pending, success, error, failure, warning")),
mcp.WithString("target_url", mcp.Description("URL for status details")),
mcp.WithString("description", mcp.Description("status description")),
mcp.WithString("context", mcp.Description("status context (e.g., ci/build)")),
)
ListStatusesTool = mcp.NewTool(
ListStatusesToolName,
mcp.WithDescription("List commit statuses for a ref"),
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("commit SHA, branch, or tag")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
GetCombinedStatusTool = mcp.NewTool(
GetCombinedStatusToolName,
mcp.WithDescription("Get the combined status for a ref"),
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("commit SHA, branch, or tag")),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: ListStatusesTool, Handler: ListStatusesFn})
Tool.RegisterRead(server.ServerTool{Tool: GetCombinedStatusTool, Handler: GetCombinedStatusFn})
Tool.RegisterWrite(server.ServerTool{Tool: CreateStatusTool, Handler: CreateStatusFn})
}
func CreateStatusFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateStatusFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
sha, _ := req.GetArguments()["sha"].(string)
state, _ := req.GetArguments()["state"].(string)
if owner == "" || repo == "" || sha == "" || state == "" {
return to.ErrorResult(errors.New("owner, repo, sha, and state are required"))
}
opt := gitea_sdk.CreateStatusOption{
State: gitea_sdk.StatusState(state),
}
if v, ok := req.GetArguments()["target_url"].(string); ok {
opt.TargetURL = v
}
if v, ok := req.GetArguments()["description"].(string); ok {
opt.Description = v
}
if v, ok := req.GetArguments()["context"].(string); ok {
opt.Context = v
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
status, _, err := client.CreateStatus(owner, repo, sha, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("create status err: %v", err))
}
return to.TextResult(status)
}
func ListStatusesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListStatusesFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
ref, _ := req.GetArguments()["ref"].(string)
if owner == "" || repo == "" || ref == "" {
return to.ErrorResult(errors.New("owner, repo, and ref are required"))
}
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
statuses, _, err := client.ListStatuses(owner, repo, ref, gitea_sdk.ListStatusesOption{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list statuses err: %v", err))
}
return to.TextResult(statuses)
}
func GetCombinedStatusFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetCombinedStatusFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
ref, _ := req.GetArguments()["ref"].(string)
if owner == "" || repo == "" || ref == "" {
return to.ErrorResult(errors.New("owner, repo, and ref are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
combined, _, err := client.GetCombinedStatus(owner, repo, ref)
if err != nil {
return to.ErrorResult(fmt.Errorf("get combined status err: %v", err))
}
return to.TextResult(combined)
}

View File

@@ -5,10 +5,10 @@ import (
"errors" "errors"
"fmt" "fmt"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"

View File

@@ -0,0 +1,200 @@
package repo
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
ListTagProtectionsToolName = "list_tag_protections"
GetTagProtectionToolName = "get_tag_protection"
CreateTagProtectionToolName = "create_tag_protection"
EditTagProtectionToolName = "edit_tag_protection"
DeleteTagProtectionToolName = "delete_tag_protection"
)
var (
ListTagProtectionsTool = mcp.NewTool(
ListTagProtectionsToolName,
mcp.WithDescription("List tag protections for a repository"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
)
GetTagProtectionTool = mcp.NewTool(
GetTagProtectionToolName,
mcp.WithDescription("Get a tag protection rule 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("tag protection ID")),
)
CreateTagProtectionTool = mcp.NewTool(
CreateTagProtectionToolName,
mcp.WithDescription("Create a tag protection rule"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("name_pattern", mcp.Required(), mcp.Description("glob pattern for tag name matching")),
mcp.WithString("whitelist_usernames", mcp.Description("comma-separated list of allowed usernames")),
mcp.WithString("whitelist_teams", mcp.Description("comma-separated list of allowed team names")),
)
EditTagProtectionTool = mcp.NewTool(
EditTagProtectionToolName,
mcp.WithDescription("Edit a tag protection rule"),
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("tag protection ID")),
mcp.WithString("name_pattern", mcp.Description("glob pattern for tag name matching")),
mcp.WithString("whitelist_usernames", mcp.Description("comma-separated list of allowed usernames")),
mcp.WithString("whitelist_teams", mcp.Description("comma-separated list of allowed team names")),
)
DeleteTagProtectionTool = mcp.NewTool(
DeleteTagProtectionToolName,
mcp.WithDescription("Delete a tag protection rule"),
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("tag protection ID")),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: ListTagProtectionsTool, Handler: ListTagProtectionsFn})
Tool.RegisterRead(server.ServerTool{Tool: GetTagProtectionTool, Handler: GetTagProtectionFn})
Tool.RegisterWrite(server.ServerTool{Tool: CreateTagProtectionTool, Handler: CreateTagProtectionFn})
Tool.RegisterWrite(server.ServerTool{Tool: EditTagProtectionTool, Handler: EditTagProtectionFn})
Tool.RegisterWrite(server.ServerTool{Tool: DeleteTagProtectionTool, Handler: DeleteTagProtectionFn})
}
func ListTagProtectionsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListTagProtectionsFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
tps, _, err := client.ListTagProtection(owner, repo, gitea_sdk.ListRepoTagProtectionsOptions{})
if err != nil {
return to.ErrorResult(fmt.Errorf("list tag protections err: %v", err))
}
return to.TextResult(tps)
}
func GetTagProtectionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetTagProtectionFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
tp, _, err := client.GetTagProtection(owner, repo, id)
if err != nil {
return to.ErrorResult(fmt.Errorf("get tag protection err: %v", err))
}
return to.TextResult(tp)
}
func CreateTagProtectionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateTagProtectionFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
namePattern, _ := req.GetArguments()["name_pattern"].(string)
if owner == "" || repo == "" || namePattern == "" {
return to.ErrorResult(errors.New("owner, repo, and name_pattern are required"))
}
opt := gitea_sdk.CreateTagProtectionOption{
NamePattern: namePattern,
}
if v, ok := req.GetArguments()["whitelist_usernames"].(string); ok && v != "" {
opt.WhitelistUsernames = splitAndTrim(v)
}
if v, ok := req.GetArguments()["whitelist_teams"].(string); ok && v != "" {
opt.WhitelistTeams = splitAndTrim(v)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
tp, _, err := client.CreateTagProtection(owner, repo, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("create tag protection err: %v", err))
}
return to.TextResult(tp)
}
func EditTagProtectionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called EditTagProtectionFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil {
return to.ErrorResult(err)
}
opt := gitea_sdk.EditTagProtectionOption{}
if v, ok := req.GetArguments()["name_pattern"].(string); ok {
opt.NamePattern = &v
}
if v, ok := req.GetArguments()["whitelist_usernames"].(string); ok {
opt.WhitelistUsernames = splitAndTrim(v)
}
if v, ok := req.GetArguments()["whitelist_teams"].(string); ok {
opt.WhitelistTeams = splitAndTrim(v)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
tp, _, err := client.EditTagProtection(owner, repo, id, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("edit tag protection err: %v", err))
}
return to.TextResult(tp)
}
func DeleteTagProtectionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteTagProtectionFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.DeleteTagProtection(owner, repo, id)
if err != nil {
return to.ErrorResult(fmt.Errorf("delete tag protection err: %v", err))
}
return to.TextResult(map[string]string{"status": "deleted"})
}

157
operation/repo/topic.go Normal file
View File

@@ -0,0 +1,157 @@
package repo
import (
"context"
"errors"
"fmt"
"strings"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
ListRepoTopicsToolName = "list_repo_topics"
SetRepoTopicsToolName = "set_repo_topics"
AddRepoTopicToolName = "add_repo_topic"
DeleteRepoTopicToolName = "delete_repo_topic"
)
var (
ListRepoTopicsTool = mcp.NewTool(
ListRepoTopicsToolName,
mcp.WithDescription("List a repository's topics"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
)
SetRepoTopicsTool = mcp.NewTool(
SetRepoTopicsToolName,
mcp.WithDescription("Replace all topics of a repository with a new list"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("topics", mcp.Required(), mcp.Description("comma-separated list of topics")),
)
AddRepoTopicTool = mcp.NewTool(
AddRepoTopicToolName,
mcp.WithDescription("Add a topic to a repository"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("topic", mcp.Required(), mcp.Description("topic name to add")),
)
DeleteRepoTopicTool = mcp.NewTool(
DeleteRepoTopicToolName,
mcp.WithDescription("Remove a topic from a repository"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("topic", mcp.Required(), mcp.Description("topic name to remove")),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: ListRepoTopicsTool, Handler: ListRepoTopicsFn})
Tool.RegisterWrite(server.ServerTool{Tool: SetRepoTopicsTool, Handler: SetRepoTopicsFn})
Tool.RegisterWrite(server.ServerTool{Tool: AddRepoTopicTool, Handler: AddRepoTopicFn})
Tool.RegisterWrite(server.ServerTool{Tool: DeleteRepoTopicTool, Handler: DeleteRepoTopicFn})
}
func ListRepoTopicsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListRepoTopicsFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
topics, _, err := client.ListRepoTopics(owner, repo, gitea_sdk.ListRepoTopicsOptions{})
if err != nil {
return to.ErrorResult(fmt.Errorf("list repo topics err: %v", err))
}
return to.TextResult(topics)
}
func SetRepoTopicsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called SetRepoTopicsFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
topicsStr, _ := req.GetArguments()["topics"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
var topics []string
if topicsStr != "" {
for _, t := range splitAndTrim(topicsStr) {
if t != "" {
topics = append(topics, t)
}
}
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.SetRepoTopics(owner, repo, topics)
if err != nil {
return to.ErrorResult(fmt.Errorf("set repo topics err: %v", err))
}
return to.TextResult(map[string]any{"status": "updated", "topics": topics})
}
func AddRepoTopicFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called AddRepoTopicFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
topic, _ := req.GetArguments()["topic"].(string)
if owner == "" || repo == "" || topic == "" {
return to.ErrorResult(errors.New("owner, repo, and topic are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.AddRepoTopic(owner, repo, topic)
if err != nil {
return to.ErrorResult(fmt.Errorf("add repo topic err: %v", err))
}
return to.TextResult(map[string]string{"status": "added", "topic": topic})
}
func DeleteRepoTopicFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteRepoTopicFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
topic, _ := req.GetArguments()["topic"].(string)
if owner == "" || repo == "" || topic == "" {
return to.ErrorResult(errors.New("owner, repo, and topic are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.DeleteRepoTopic(owner, repo, topic)
if err != nil {
return to.ErrorResult(fmt.Errorf("delete repo topic err: %v", err))
}
return to.TextResult(map[string]string{"status": "deleted", "topic": topic})
}
func splitAndTrim(s string) []string {
var result []string
for _, part := range strings.Split(s, ",") {
trimmed := strings.TrimSpace(part)
if trimmed != "" {
result = append(result, trimmed)
}
}
return result
}

167
operation/repo/transfer.go Normal file
View File

@@ -0,0 +1,167 @@
package repo
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
TransferRepoToolName = "transfer_repo"
AcceptRepoTransferToolName = "accept_repo_transfer"
RejectRepoTransferToolName = "reject_repo_transfer"
CreateFromTemplateToolName = "create_repo_from_template"
)
var (
TransferRepoTool = mcp.NewTool(
TransferRepoToolName,
mcp.WithDescription("Transfer a repository to a new owner"),
mcp.WithString("owner", mcp.Required(), mcp.Description("current repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("new_owner", mcp.Required(), mcp.Description("new owner username or org")),
)
AcceptRepoTransferTool = mcp.NewTool(
AcceptRepoTransferToolName,
mcp.WithDescription("Accept a pending repository transfer"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
)
RejectRepoTransferTool = mcp.NewTool(
RejectRepoTransferToolName,
mcp.WithDescription("Reject a pending repository transfer"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
)
CreateFromTemplateTool = mcp.NewTool(
CreateFromTemplateToolName,
mcp.WithDescription("Create a repository from a template"),
mcp.WithString("template_owner", mcp.Required(), mcp.Description("template repository owner")),
mcp.WithString("template_repo", mcp.Required(), mcp.Description("template repository name")),
mcp.WithString("name", mcp.Required(), mcp.Description("name for the new repository")),
mcp.WithString("owner", mcp.Required(), mcp.Description("owner of the new repository (user or org)")),
mcp.WithString("description", mcp.Description("description for the new repository")),
mcp.WithBoolean("private", mcp.Description("whether the new repository is private")),
mcp.WithBoolean("git_content", mcp.Description("copy git content from template (default: true)")),
mcp.WithBoolean("topics", mcp.Description("copy topics from template")),
mcp.WithBoolean("labels", mcp.Description("copy labels from template")),
mcp.WithBoolean("webhooks", mcp.Description("copy webhooks from template")),
)
)
func init() {
Tool.RegisterWrite(server.ServerTool{Tool: TransferRepoTool, Handler: TransferRepoFn})
Tool.RegisterWrite(server.ServerTool{Tool: AcceptRepoTransferTool, Handler: AcceptRepoTransferFn})
Tool.RegisterWrite(server.ServerTool{Tool: RejectRepoTransferTool, Handler: RejectRepoTransferFn})
Tool.RegisterWrite(server.ServerTool{Tool: CreateFromTemplateTool, Handler: CreateFromTemplateFn})
}
func TransferRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called TransferRepoFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
newOwner, _ := req.GetArguments()["new_owner"].(string)
if owner == "" || repo == "" || newOwner == "" {
return to.ErrorResult(errors.New("owner, repo, and new_owner are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
repository, _, err := client.TransferRepo(owner, repo, gitea_sdk.TransferRepoOption{
NewOwner: newOwner,
})
if err != nil {
return to.ErrorResult(fmt.Errorf("transfer repo err: %v", err))
}
return to.TextResult(repository)
}
func AcceptRepoTransferFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called AcceptRepoTransferFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
repository, _, err := client.AcceptRepoTransfer(owner, repo)
if err != nil {
return to.ErrorResult(fmt.Errorf("accept repo transfer err: %v", err))
}
return to.TextResult(repository)
}
func RejectRepoTransferFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called RejectRepoTransferFn")
owner, _ := req.GetArguments()["owner"].(string)
repo, _ := req.GetArguments()["repo"].(string)
if owner == "" || repo == "" {
return to.ErrorResult(errors.New("owner and repo are required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
repository, _, err := client.RejectRepoTransfer(owner, repo)
if err != nil {
return to.ErrorResult(fmt.Errorf("reject repo transfer err: %v", err))
}
return to.TextResult(repository)
}
func CreateFromTemplateFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateFromTemplateFn")
templateOwner, _ := req.GetArguments()["template_owner"].(string)
templateRepo, _ := req.GetArguments()["template_repo"].(string)
name, _ := req.GetArguments()["name"].(string)
owner, _ := req.GetArguments()["owner"].(string)
if templateOwner == "" || templateRepo == "" || name == "" || owner == "" {
return to.ErrorResult(errors.New("template_owner, template_repo, name, and owner are required"))
}
opt := gitea_sdk.CreateRepoFromTemplateOption{
Name: name,
Owner: owner,
}
if v, ok := req.GetArguments()["description"].(string); ok {
opt.Description = v
}
if v, ok := req.GetArguments()["private"].(bool); ok {
opt.Private = v
}
if v, ok := req.GetArguments()["git_content"].(bool); ok {
opt.GitContent = v
}
if v, ok := req.GetArguments()["topics"].(bool); ok {
opt.Topics = v
}
if v, ok := req.GetArguments()["labels"].(bool); ok {
opt.Labels = v
}
if v, ok := req.GetArguments()["webhooks"].(bool); ok {
opt.Webhooks = v
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
repository, _, err := client.CreateRepoFromTemplate(templateOwner, templateRepo, opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("create from template err: %v", err))
}
return to.TextResult(repository)
}

View File

@@ -5,11 +5,11 @@ import (
"errors" "errors"
"fmt" "fmt"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
"gitea.com/gitea/gitea-mcp/pkg/tool" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/tool"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"

View File

@@ -0,0 +1,104 @@
package settings
import (
"context"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/tool"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
var Tool = tool.New()
const (
GetAPISettingsToolName = "get_api_settings"
GetAttachmentSettingsToolName = "get_attachment_settings"
GetRepoSettingsToolName = "get_repo_settings"
GetUISettingsToolName = "get_ui_settings"
)
var (
GetAPISettingsTool = mcp.NewTool(
GetAPISettingsToolName,
mcp.WithDescription("Get the global API settings of the Gitea instance"),
)
GetAttachmentSettingsTool = mcp.NewTool(
GetAttachmentSettingsToolName,
mcp.WithDescription("Get the global attachment settings"),
)
GetRepoSettingsTool = mcp.NewTool(
GetRepoSettingsToolName,
mcp.WithDescription("Get the global repository settings"),
)
GetUISettingsTool = mcp.NewTool(
GetUISettingsToolName,
mcp.WithDescription("Get the global UI settings"),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: GetAPISettingsTool, Handler: GetAPISettingsFn})
Tool.RegisterRead(server.ServerTool{Tool: GetAttachmentSettingsTool, Handler: GetAttachmentSettingsFn})
Tool.RegisterRead(server.ServerTool{Tool: GetRepoSettingsTool, Handler: GetRepoSettingsFn})
Tool.RegisterRead(server.ServerTool{Tool: GetUISettingsTool, Handler: GetUISettingsFn})
}
func GetAPISettingsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetAPISettingsFn")
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
settings, _, err := client.GetGlobalAPISettings()
if err != nil {
return to.ErrorResult(fmt.Errorf("get API settings err: %v", err))
}
return to.TextResult(settings)
}
func GetAttachmentSettingsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetAttachmentSettingsFn")
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
settings, _, err := client.GetGlobalAttachmentSettings()
if err != nil {
return to.ErrorResult(fmt.Errorf("get attachment settings err: %v", err))
}
return to.TextResult(settings)
}
func GetRepoSettingsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetRepoSettingsFn")
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
settings, _, err := client.GetGlobalRepoSettings()
if err != nil {
return to.ErrorResult(fmt.Errorf("get repo settings err: %v", err))
}
return to.TextResult(settings)
}
func GetUISettingsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetUISettingsFn")
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
settings, _, err := client.GetGlobalUISettings()
if err != nil {
return to.ErrorResult(fmt.Errorf("get UI settings err: %v", err))
}
return to.TextResult(settings)
}

View File

@@ -7,11 +7,11 @@ import (
"fmt" "fmt"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "code.gitea.io/sdk/gitea"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
"gitea.com/gitea/gitea-mcp/pkg/tool" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/tool"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"

125
operation/user/blocks.go Normal file
View File

@@ -0,0 +1,125 @@
package user
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
ListMyBlocksToolName = "list_my_blocks"
CheckUserBlockToolName = "check_user_block"
BlockUserToolName = "block_user"
UnblockUserToolName = "unblock_user"
)
var (
ListMyBlocksTool = mcp.NewTool(
ListMyBlocksToolName,
mcp.WithDescription("List users blocked by the authenticated user"),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
CheckUserBlockTool = mcp.NewTool(
CheckUserBlockToolName,
mcp.WithDescription("Check if the authenticated user has blocked a user"),
mcp.WithString("username", mcp.Required(), mcp.Description("username to check")),
)
BlockUserTool = mcp.NewTool(
BlockUserToolName,
mcp.WithDescription("Block a user"),
mcp.WithString("username", mcp.Required(), mcp.Description("username to block")),
)
UnblockUserTool = mcp.NewTool(
UnblockUserToolName,
mcp.WithDescription("Unblock a user"),
mcp.WithString("username", mcp.Required(), mcp.Description("username to unblock")),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: ListMyBlocksTool, Handler: ListMyBlocksFn})
Tool.RegisterRead(server.ServerTool{Tool: CheckUserBlockTool, Handler: CheckUserBlockFn})
Tool.RegisterWrite(server.ServerTool{Tool: BlockUserTool, Handler: BlockUserFn})
Tool.RegisterWrite(server.ServerTool{Tool: UnblockUserTool, Handler: UnblockUserFn})
}
func ListMyBlocksFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListMyBlocksFn")
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
users, _, err := client.ListMyBlocks(gitea_sdk.ListUserBlocksOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list my blocks err: %v", err))
}
return to.TextResult(users)
}
func CheckUserBlockFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CheckUserBlockFn")
username, ok := req.GetArguments()["username"].(string)
if !ok {
return to.ErrorResult(errors.New("username is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
isBlocked, _, err := client.CheckUserBlock(username)
if err != nil {
return to.ErrorResult(fmt.Errorf("check block %s err: %v", username, err))
}
return to.TextResult(map[string]any{"username": username, "is_blocked": isBlocked})
}
func BlockUserFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called BlockUserFn")
username, ok := req.GetArguments()["username"].(string)
if !ok {
return to.ErrorResult(errors.New("username is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.BlockUser(username)
if err != nil {
return to.ErrorResult(fmt.Errorf("block user %s err: %v", username, err))
}
return to.TextResult(map[string]string{"status": "blocked", "username": username})
}
func UnblockUserFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called UnblockUserFn")
username, ok := req.GetArguments()["username"].(string)
if !ok {
return to.ErrorResult(errors.New("username is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.UnblockUser(username)
if err != nil {
return to.ErrorResult(fmt.Errorf("unblock user %s err: %v", username, err))
}
return to.TextResult(map[string]string{"status": "unblocked", "username": username})
}

119
operation/user/emails.go Normal file
View File

@@ -0,0 +1,119 @@
package user
import (
"context"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
ListEmailsToolName = "list_emails"
AddEmailToolName = "add_email"
DeleteEmailToolName = "delete_email"
)
var (
ListEmailsTool = mcp.NewTool(
ListEmailsToolName,
mcp.WithDescription("List the authenticated user's email addresses"),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
AddEmailTool = mcp.NewTool(
AddEmailToolName,
mcp.WithDescription("Add email addresses for the authenticated user"),
mcp.WithArray("emails", mcp.Required(), mcp.Description("email addresses to add"), mcp.Items(map[string]any{"type": "string"})),
)
DeleteEmailTool = mcp.NewTool(
DeleteEmailToolName,
mcp.WithDescription("Delete email addresses for the authenticated user"),
mcp.WithArray("emails", mcp.Required(), mcp.Description("email addresses to delete"), mcp.Items(map[string]any{"type": "string"})),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: ListEmailsTool, Handler: ListEmailsFn})
Tool.RegisterWrite(server.ServerTool{Tool: AddEmailTool, Handler: AddEmailFn})
Tool.RegisterWrite(server.ServerTool{Tool: DeleteEmailTool, Handler: DeleteEmailFn})
}
func ListEmailsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListEmailsFn")
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
emails, _, err := client.ListEmails(gitea_sdk.ListEmailsOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list emails err: %v", err))
}
return to.TextResult(emails)
}
func AddEmailFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called AddEmailFn")
emailsArg, exists := req.GetArguments()["emails"]
if !exists {
return to.ErrorResult(fmt.Errorf("emails is required"))
}
emailsSlice, ok := emailsArg.([]any)
if !ok {
return to.ErrorResult(fmt.Errorf("emails must be an array"))
}
emails := make([]string, 0, len(emailsSlice))
for _, e := range emailsSlice {
if s, ok := e.(string); ok {
emails = append(emails, s)
}
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
result, _, err := client.AddEmail(gitea_sdk.CreateEmailOption{Emails: emails})
if err != nil {
return to.ErrorResult(fmt.Errorf("add emails err: %v", err))
}
return to.TextResult(result)
}
func DeleteEmailFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteEmailFn")
emailsArg, exists := req.GetArguments()["emails"]
if !exists {
return to.ErrorResult(fmt.Errorf("emails is required"))
}
emailsSlice, ok := emailsArg.([]any)
if !ok {
return to.ErrorResult(fmt.Errorf("emails must be an array"))
}
emails := make([]string, 0, len(emailsSlice))
for _, e := range emailsSlice {
if s, ok := e.(string); ok {
emails = append(emails, s)
}
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.DeleteEmail(gitea_sdk.DeleteEmailOption{Emails: emails})
if err != nil {
return to.ErrorResult(fmt.Errorf("delete emails err: %v", err))
}
return to.TextResult(map[string]any{"status": "deleted", "emails": emails})
}

210
operation/user/followers.go Normal file
View File

@@ -0,0 +1,210 @@
package user
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
ListMyFollowersToolName = "list_my_followers"
ListMyFollowingToolName = "list_my_following"
ListUserFollowersToolName = "list_user_followers"
ListUserFollowingToolName = "list_user_following"
CheckFollowingToolName = "check_following"
FollowUserToolName = "follow_user"
UnfollowUserToolName = "unfollow_user"
)
var (
ListMyFollowersTool = mcp.NewTool(
ListMyFollowersToolName,
mcp.WithDescription("List the authenticated user's followers"),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
ListMyFollowingTool = mcp.NewTool(
ListMyFollowingToolName,
mcp.WithDescription("List users the authenticated user is following"),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
ListUserFollowersTool = mcp.NewTool(
ListUserFollowersToolName,
mcp.WithDescription("List a user's followers"),
mcp.WithString("username", mcp.Required(), mcp.Description("username")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
ListUserFollowingTool = mcp.NewTool(
ListUserFollowingToolName,
mcp.WithDescription("List users a user is following"),
mcp.WithString("username", mcp.Required(), mcp.Description("username")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
CheckFollowingTool = mcp.NewTool(
CheckFollowingToolName,
mcp.WithDescription("Check if the authenticated user is following a user"),
mcp.WithString("username", mcp.Required(), mcp.Description("username to check")),
)
FollowUserTool = mcp.NewTool(
FollowUserToolName,
mcp.WithDescription("Follow a user"),
mcp.WithString("username", mcp.Required(), mcp.Description("username to follow")),
)
UnfollowUserTool = mcp.NewTool(
UnfollowUserToolName,
mcp.WithDescription("Unfollow a user"),
mcp.WithString("username", mcp.Required(), mcp.Description("username to unfollow")),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: ListMyFollowersTool, Handler: ListMyFollowersFn})
Tool.RegisterRead(server.ServerTool{Tool: ListMyFollowingTool, Handler: ListMyFollowingFn})
Tool.RegisterRead(server.ServerTool{Tool: ListUserFollowersTool, Handler: ListUserFollowersFn})
Tool.RegisterRead(server.ServerTool{Tool: ListUserFollowingTool, Handler: ListUserFollowingFn})
Tool.RegisterRead(server.ServerTool{Tool: CheckFollowingTool, Handler: CheckFollowingFn})
Tool.RegisterWrite(server.ServerTool{Tool: FollowUserTool, Handler: FollowUserFn})
Tool.RegisterWrite(server.ServerTool{Tool: UnfollowUserTool, Handler: UnfollowUserFn})
}
func ListMyFollowersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListMyFollowersFn")
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
followers, _, err := client.ListMyFollowers(gitea_sdk.ListFollowersOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list my followers err: %v", err))
}
return to.TextResult(followers)
}
func ListMyFollowingFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListMyFollowingFn")
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
following, _, err := client.ListMyFollowing(gitea_sdk.ListFollowingOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list my following err: %v", err))
}
return to.TextResult(following)
}
func ListUserFollowersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListUserFollowersFn")
username, ok := req.GetArguments()["username"].(string)
if !ok {
return to.ErrorResult(errors.New("username is required"))
}
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
followers, _, err := client.ListFollowers(username, gitea_sdk.ListFollowersOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list %s followers err: %v", username, err))
}
return to.TextResult(followers)
}
func ListUserFollowingFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListUserFollowingFn")
username, ok := req.GetArguments()["username"].(string)
if !ok {
return to.ErrorResult(errors.New("username is required"))
}
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
following, _, err := client.ListFollowing(username, gitea_sdk.ListFollowingOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list %s following err: %v", username, err))
}
return to.TextResult(following)
}
func CheckFollowingFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CheckFollowingFn")
username, ok := req.GetArguments()["username"].(string)
if !ok {
return to.ErrorResult(errors.New("username is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
isFollowing, _ := client.IsFollowing(username)
return to.TextResult(map[string]any{"username": username, "is_following": isFollowing})
}
func FollowUserFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called FollowUserFn")
username, ok := req.GetArguments()["username"].(string)
if !ok {
return to.ErrorResult(errors.New("username is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.Follow(username)
if err != nil {
return to.ErrorResult(fmt.Errorf("follow user %s err: %v", username, err))
}
return to.TextResult(map[string]string{"status": "following", "username": username})
}
func UnfollowUserFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called UnfollowUserFn")
username, ok := req.GetArguments()["username"].(string)
if !ok {
return to.ErrorResult(errors.New("username is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.Unfollow(username)
if err != nil {
return to.ErrorResult(fmt.Errorf("unfollow user %s err: %v", username, err))
}
return to.TextResult(map[string]string{"status": "unfollowed", "username": username})
}

169
operation/user/keys.go Normal file
View File

@@ -0,0 +1,169 @@
package user
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
ListMyPublicKeysToolName = "list_my_public_keys"
ListUserPublicKeysToolName = "list_user_public_keys"
GetPublicKeyToolName = "get_public_key"
CreatePublicKeyToolName = "create_public_key"
DeletePublicKeyToolName = "delete_public_key"
)
var (
ListMyPublicKeysTool = mcp.NewTool(
ListMyPublicKeysToolName,
mcp.WithDescription("List the authenticated user's SSH public keys"),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
ListUserPublicKeysTool = mcp.NewTool(
ListUserPublicKeysToolName,
mcp.WithDescription("List a user's SSH public keys"),
mcp.WithString("username", mcp.Required(), mcp.Description("username")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
GetPublicKeyTool = mcp.NewTool(
GetPublicKeyToolName,
mcp.WithDescription("Get an SSH public key by ID"),
mcp.WithNumber("id", mcp.Required(), mcp.Description("key ID")),
)
CreatePublicKeyTool = mcp.NewTool(
CreatePublicKeyToolName,
mcp.WithDescription("Create an SSH public key for the authenticated user"),
mcp.WithString("title", mcp.Required(), mcp.Description("key title")),
mcp.WithString("key", mcp.Required(), mcp.Description("SSH public key content")),
mcp.WithBoolean("read_only", mcp.Description("whether the key is read-only")),
)
DeletePublicKeyTool = mcp.NewTool(
DeletePublicKeyToolName,
mcp.WithDescription("Delete an SSH public key"),
mcp.WithNumber("id", mcp.Required(), mcp.Description("key ID")),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: ListMyPublicKeysTool, Handler: ListMyPublicKeysFn})
Tool.RegisterRead(server.ServerTool{Tool: ListUserPublicKeysTool, Handler: ListUserPublicKeysFn})
Tool.RegisterRead(server.ServerTool{Tool: GetPublicKeyTool, Handler: GetPublicKeyFn})
Tool.RegisterWrite(server.ServerTool{Tool: CreatePublicKeyTool, Handler: CreatePublicKeyFn})
Tool.RegisterWrite(server.ServerTool{Tool: DeletePublicKeyTool, Handler: DeletePublicKeyFn})
}
func ListMyPublicKeysFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListMyPublicKeysFn")
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
keys, _, err := client.ListMyPublicKeys(gitea_sdk.ListPublicKeysOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list my public keys err: %v", err))
}
return to.TextResult(keys)
}
func ListUserPublicKeysFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListUserPublicKeysFn")
username, ok := req.GetArguments()["username"].(string)
if !ok {
return to.ErrorResult(errors.New("username is required"))
}
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
keys, _, err := client.ListPublicKeys(username, gitea_sdk.ListPublicKeysOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list %s public keys err: %v", username, err))
}
return to.TextResult(keys)
}
func GetPublicKeyFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetPublicKeyFn")
id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
key, _, err := client.GetPublicKey(id)
if err != nil {
return to.ErrorResult(fmt.Errorf("get public key %d err: %v", id, err))
}
return to.TextResult(key)
}
func CreatePublicKeyFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreatePublicKeyFn")
title, ok := req.GetArguments()["title"].(string)
if !ok {
return to.ErrorResult(errors.New("title is required"))
}
key, ok := req.GetArguments()["key"].(string)
if !ok {
return to.ErrorResult(errors.New("key is required"))
}
opt := gitea_sdk.CreateKeyOption{
Title: title,
Key: key,
}
if v, ok := req.GetArguments()["read_only"].(bool); ok {
opt.ReadOnly = v
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
pubKey, _, err := client.CreatePublicKey(opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("create public key err: %v", err))
}
return to.TextResult(pubKey)
}
func DeletePublicKeyFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeletePublicKeyFn")
id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil {
return to.ErrorResult(err)
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.DeletePublicKey(id)
if err != nil {
return to.ErrorResult(fmt.Errorf("delete public key %d err: %v", id, err))
}
return to.TextResult(map[string]any{"status": "deleted", "key_id": id})
}

121
operation/user/profile.go Normal file
View File

@@ -0,0 +1,121 @@
package user
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
GetUserInfoToolName = "get_user_info"
GetUserSettingsToolName = "get_user_settings"
UpdateUserSettingsToolName = "update_user_settings"
)
var (
GetUserInfoTool = mcp.NewTool(
GetUserInfoToolName,
mcp.WithDescription("Get a user's public information by username"),
mcp.WithString("username", mcp.Required(), mcp.Description("username")),
)
GetUserSettingsTool = mcp.NewTool(
GetUserSettingsToolName,
mcp.WithDescription("Get the authenticated user's settings"),
)
UpdateUserSettingsTool = mcp.NewTool(
UpdateUserSettingsToolName,
mcp.WithDescription("Update the authenticated user's settings"),
mcp.WithString("full_name", mcp.Description("full name")),
mcp.WithString("description", mcp.Description("user bio/description")),
mcp.WithString("website", mcp.Description("website URL")),
mcp.WithString("location", mcp.Description("location")),
mcp.WithString("language", mcp.Description("language preference")),
mcp.WithString("theme", mcp.Description("UI theme")),
mcp.WithBoolean("hide_email", mcp.Description("hide email from profile")),
mcp.WithBoolean("hide_activity", mcp.Description("hide activity from profile")),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: GetUserInfoTool, Handler: GetUserByNameFn})
Tool.RegisterRead(server.ServerTool{Tool: GetUserSettingsTool, Handler: GetUserSettingsFn})
Tool.RegisterWrite(server.ServerTool{Tool: UpdateUserSettingsTool, Handler: UpdateUserSettingsFn})
}
func GetUserByNameFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetUserByNameFn")
username, ok := req.GetArguments()["username"].(string)
if !ok {
return to.ErrorResult(errors.New("username is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
user, _, err := client.GetUserInfo(username)
if err != nil {
return to.ErrorResult(fmt.Errorf("get user %s err: %v", username, err))
}
return to.TextResult(user)
}
func GetUserSettingsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetUserSettingsFn")
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
settings, _, err := client.GetUserSettings()
if err != nil {
return to.ErrorResult(fmt.Errorf("get user settings err: %v", err))
}
return to.TextResult(settings)
}
func UpdateUserSettingsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called UpdateUserSettingsFn")
opt := gitea_sdk.UserSettingsOptions{}
if v, ok := req.GetArguments()["full_name"].(string); ok {
opt.FullName = &v
}
if v, ok := req.GetArguments()["description"].(string); ok {
opt.Description = &v
}
if v, ok := req.GetArguments()["website"].(string); ok {
opt.Website = &v
}
if v, ok := req.GetArguments()["location"].(string); ok {
opt.Location = &v
}
if v, ok := req.GetArguments()["language"].(string); ok {
opt.Language = &v
}
if v, ok := req.GetArguments()["theme"].(string); ok {
opt.Theme = &v
}
if v, ok := req.GetArguments()["hide_email"].(bool); ok {
opt.HideEmail = &v
}
if v, ok := req.GetArguments()["hide_activity"].(bool); ok {
opt.HideActivity = &v
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
settings, _, err := client.UpdateUserSettings(opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("update user settings err: %v", err))
}
return to.TextResult(settings)
}

254
operation/user/repos.go Normal file
View File

@@ -0,0 +1,254 @@
package user
import (
"context"
"errors"
"fmt"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
const (
ListUserReposToolName = "list_user_repos"
ListMyStarredToolName = "list_my_starred"
StarRepoToolName = "star_repo"
UnstarRepoToolName = "unstar_repo"
CheckStarredToolName = "check_starred"
ListMySubscriptionsToolName = "list_my_subscriptions"
ListUserHeatmapToolName = "list_user_heatmap"
ListUserActivityToolName = "list_user_activity"
)
var (
ListUserReposTool = mcp.NewTool(
ListUserReposToolName,
mcp.WithDescription("List a user's repositories"),
mcp.WithString("username", mcp.Required(), mcp.Description("username")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
ListMyStarredTool = mcp.NewTool(
ListMyStarredToolName,
mcp.WithDescription("List repositories starred by the authenticated user"),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
StarRepoTool = mcp.NewTool(
StarRepoToolName,
mcp.WithDescription("Star a repository"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
)
UnstarRepoTool = mcp.NewTool(
UnstarRepoToolName,
mcp.WithDescription("Unstar a repository"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
)
CheckStarredTool = mcp.NewTool(
CheckStarredToolName,
mcp.WithDescription("Check if the authenticated user has starred a repository"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
)
ListMySubscriptionsTool = mcp.NewTool(
ListMySubscriptionsToolName,
mcp.WithDescription("List repositories watched by the authenticated user"),
)
ListUserHeatmapTool = mcp.NewTool(
ListUserHeatmapToolName,
mcp.WithDescription("Get a user's contribution heatmap data"),
mcp.WithString("username", mcp.Required(), mcp.Description("username")),
)
ListUserActivityTool = mcp.NewTool(
ListUserActivityToolName,
mcp.WithDescription("List a user's activity feeds"),
mcp.WithString("username", mcp.Required(), mcp.Description("username")),
mcp.WithNumber("page", mcp.Description("page number"), mcp.DefaultNumber(1)),
mcp.WithNumber("pageSize", mcp.Description("page size"), mcp.DefaultNumber(100)),
)
)
func init() {
Tool.RegisterRead(server.ServerTool{Tool: ListUserReposTool, Handler: ListUserReposFn})
Tool.RegisterRead(server.ServerTool{Tool: ListMyStarredTool, Handler: ListMyStarredFn})
Tool.RegisterRead(server.ServerTool{Tool: CheckStarredTool, Handler: CheckStarredFn})
Tool.RegisterRead(server.ServerTool{Tool: ListMySubscriptionsTool, Handler: ListMySubscriptionsFn})
Tool.RegisterRead(server.ServerTool{Tool: ListUserHeatmapTool, Handler: ListUserHeatmapFn})
Tool.RegisterRead(server.ServerTool{Tool: ListUserActivityTool, Handler: ListUserActivityFn})
Tool.RegisterWrite(server.ServerTool{Tool: StarRepoTool, Handler: StarRepoFn})
Tool.RegisterWrite(server.ServerTool{Tool: UnstarRepoTool, Handler: UnstarRepoFn})
}
func ListUserReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListUserReposFn")
username, ok := req.GetArguments()["username"].(string)
if !ok {
return to.ErrorResult(errors.New("username is required"))
}
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
repos, _, err := client.ListUserRepos(username, gitea_sdk.ListReposOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list %s repos err: %v", username, err))
}
return to.TextResult(repos)
}
func ListMyStarredFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListMyStarredFn")
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
repos, _, err := client.GetMyStarredRepos()
if err != nil {
return to.ErrorResult(fmt.Errorf("list my starred repos err: %v", err))
}
// Manual pagination since SDK returns all
start := (int(page) - 1) * int(pageSize)
if start >= len(repos) {
return to.TextResult([]*gitea_sdk.Repository{})
}
end := start + int(pageSize)
if end > len(repos) {
end = len(repos)
}
return to.TextResult(repos[start:end])
}
func StarRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called StarRepoFn")
owner, ok := req.GetArguments()["owner"].(string)
if !ok {
return to.ErrorResult(errors.New("owner is required"))
}
repo, ok := req.GetArguments()["repo"].(string)
if !ok {
return to.ErrorResult(errors.New("repo is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.StarRepo(owner, repo)
if err != nil {
return to.ErrorResult(fmt.Errorf("star %s/%s err: %v", owner, repo, err))
}
return to.TextResult(map[string]string{"status": "starred", "owner": owner, "repo": repo})
}
func UnstarRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called UnstarRepoFn")
owner, ok := req.GetArguments()["owner"].(string)
if !ok {
return to.ErrorResult(errors.New("owner is required"))
}
repo, ok := req.GetArguments()["repo"].(string)
if !ok {
return to.ErrorResult(errors.New("repo is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.UnStarRepo(owner, repo)
if err != nil {
return to.ErrorResult(fmt.Errorf("unstar %s/%s err: %v", owner, repo, err))
}
return to.TextResult(map[string]string{"status": "unstarred", "owner": owner, "repo": repo})
}
func CheckStarredFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CheckStarredFn")
owner, ok := req.GetArguments()["owner"].(string)
if !ok {
return to.ErrorResult(errors.New("owner is required"))
}
repo, ok := req.GetArguments()["repo"].(string)
if !ok {
return to.ErrorResult(errors.New("repo is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
isStarred, _, err := client.IsRepoStarring(owner, repo)
if err != nil {
return to.ErrorResult(fmt.Errorf("check starred %s/%s err: %v", owner, repo, err))
}
return to.TextResult(map[string]any{"owner": owner, "repo": repo, "is_starred": isStarred})
}
func ListMySubscriptionsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListMySubscriptionsFn")
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
repos, _, err := client.GetMyWatchedRepos()
if err != nil {
return to.ErrorResult(fmt.Errorf("list my subscriptions err: %v", err))
}
return to.TextResult(repos)
}
func ListUserHeatmapFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListUserHeatmapFn")
username, ok := req.GetArguments()["username"].(string)
if !ok {
return to.ErrorResult(errors.New("username is required"))
}
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
heatmap, _, err := client.GetUserHeatmap(username)
if err != nil {
return to.ErrorResult(fmt.Errorf("get %s heatmap err: %v", username, err))
}
return to.TextResult(heatmap)
}
func ListUserActivityFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListUserActivityFn")
username, ok := req.GetArguments()["username"].(string)
if !ok {
return to.ErrorResult(errors.New("username is required"))
}
page := params.GetOptionalInt(req.GetArguments(), "page", 1)
pageSize := params.GetOptionalInt(req.GetArguments(), "pageSize", 100)
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
feeds, _, err := client.ListUserActivityFeeds(username, gitea_sdk.ListUserActivityFeedsOptions{
ListOptions: gitea_sdk.ListOptions{Page: int(page), PageSize: int(pageSize)},
})
if err != nil {
return to.ErrorResult(fmt.Errorf("list %s activity err: %v", username, err))
}
return to.TextResult(feeds)
}

View File

@@ -4,11 +4,11 @@ import (
"context" "context"
"fmt" "fmt"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
"gitea.com/gitea/gitea-mcp/pkg/tool" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/tool"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "code.gitea.io/sdk/gitea"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"

View File

@@ -4,10 +4,10 @@ import (
"context" "context"
"fmt" "fmt"
"gitea.com/gitea/gitea-mcp/pkg/flag" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/flag"
"gitea.com/gitea/gitea-mcp/pkg/log" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/to" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
"gitea.com/gitea/gitea-mcp/pkg/tool" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/tool"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"

View File

@@ -6,10 +6,10 @@ import (
"fmt" "fmt"
"net/url" "net/url"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/to" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/to"
"gitea.com/gitea/gitea-mcp/pkg/tool" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/tool"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"

View File

@@ -7,8 +7,8 @@ import (
"net/http" "net/http"
"code.gitea.io/sdk/gitea" "code.gitea.io/sdk/gitea"
mcpContext "gitea.com/gitea/gitea-mcp/pkg/context" mcpContext "git.lethalbits.com/lethalbits/gitea-mcp/pkg/context"
"gitea.com/gitea/gitea-mcp/pkg/flag" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/flag"
) )
func NewClient(token string) (*gitea.Client, error) { func NewClient(token string) (*gitea.Client, error) {

View File

@@ -13,8 +13,8 @@ import (
"strings" "strings"
"time" "time"
mcpContext "gitea.com/gitea/gitea-mcp/pkg/context" mcpContext "git.lethalbits.com/lethalbits/gitea-mcp/pkg/context"
"gitea.com/gitea/gitea-mcp/pkg/flag" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/flag"
) )
type HTTPError struct { type HTTPError struct {

View File

@@ -4,8 +4,8 @@ import (
"context" "context"
"testing" "testing"
mcpContext "gitea.com/gitea/gitea-mcp/pkg/context" mcpContext "git.lethalbits.com/lethalbits/gitea-mcp/pkg/context"
"gitea.com/gitea/gitea-mcp/pkg/flag" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/flag"
) )
func TestTokenFromContext(t *testing.T) { func TestTokenFromContext(t *testing.T) {

View File

@@ -5,7 +5,7 @@ import (
"sync" "sync"
"time" "time"
"gitea.com/gitea/gitea-mcp/pkg/flag" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/flag"
"go.uber.org/zap" "go.uber.org/zap"
"go.uber.org/zap/zapcore" "go.uber.org/zap/zapcore"
"gopkg.in/natefinch/lumberjack.v2" "gopkg.in/natefinch/lumberjack.v2"

View File

@@ -4,7 +4,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"gitea.com/gitea/gitea-mcp/pkg/log" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/log"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
) )

View File

@@ -1,7 +1,7 @@
package tool package tool
import ( import (
"gitea.com/gitea/gitea-mcp/pkg/flag" "git.lethalbits.com/lethalbits/gitea-mcp/pkg/flag"
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"
) )