26 Commits
v0.1.9 ... sec

Author SHA1 Message Date
appleboy
9000494a63 ci: correct Trivy action repository reference format
- Update Trivy action reference to use the correct repository format

Signed-off-by: appleboy <appleboy.tw@gmail.com>
2025-07-31 22:03:12 +08:00
appleboy
3d44b2f8d6 ci: update Trivy action to use full GitHub URL reference
- Change Trivy action reference to use the full Git URL for aquasecurity/trivy-action

Signed-off-by: appleboy <appleboy.tw@gmail.com>
2025-07-31 21:57:34 +08:00
appleboy
7ce07265b9 ci: integrate Trivy code scanning in PR workflows
- Add a code scanning job using Trivy to check for CRITICAL and HIGH severity vulnerabilities during PR workflows

Signed-off-by: appleboy <appleboy.tw@gmail.com>
2025-07-31 21:23:55 +08:00
appleboy
eb6b5a8f92 chore: upgrade Go dependencies to latest stable versions (#73)
- Bump github.com/mark3labs/mcp-go dependency to v0.35.0
- Update github.com/spf13/cast to v1.9.2
- Upgrade golang.org/x/crypto to v0.40.0
- Upgrade golang.org/x/sys to v0.34.0

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

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/73
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2025-07-27 06:23:38 +00:00
hiifong
1d9bdb5b44 fix bug 2025-07-21 09:04:37 +00:00
Bo-Yi Wu
093cddbcb6 feat: configure HTTP server heartbeat interval to 30 seconds
- Import the time package to support time-based configuration
- Set the HTTP server's heartbeat interval to 30 seconds using a new option in its initialization

Signed-off-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2025-07-18 10:12:26 +08:00
appleboy
5dbfe21127 refactor: refactor logging and server setup for clarity and structure (#64)
- Refactor server initialization calls in Run to use multiline construction style and explicitly pass options in HTTP mode
- Fix logic in Default to prevent redundant logger initialization
- Remove unused Logger function and introduce a Logger struct with Infof and Errorf methods for structured logging
- Add a New function for creating instances of the Logger struct

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

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/64
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2025-06-22 10:27:09 +00:00
Alex Kirhenshtein
b85a523983 Bump go-mcp version to 0.32.0 to mitigate Claude desktop connectivity issue (#63)
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/63
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-by: Bo-Yi Wu (吳柏毅) <appleboy.tw@gmail.com>
Co-authored-by: Alex Kirhenshtein <alk@netxms.org>
Co-committed-by: Alex Kirhenshtein <alk@netxms.org>
2025-06-21 03:34:17 +00:00
appleboy
da08718e24 style: refactor code formatting for clarity and conciseness
- Remove extra blank lines for cleaner code formatting
- Combine variable declaration of GetGiteaMCPServerVersionTool into a single line for clarity

Signed-off-by: appleboy <appleboy.tw@gmail.com>
2025-06-15 19:44:28 +08:00
Bo-Yi Wu
44ea8969f4 refactor: migrate environment config from GITEA_MODE to MCP_MODE (#62)
- Remove the GITEA_MODE environment variable from the Dockerfile
- Switch environment variable usage from GITEA_MODE to MCP_MODE in the Go command initialization

fix https://gitea.com/gitea/gitea-mcp/issues/55

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

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/62
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-committed-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2025-06-15 11:40:59 +00:00
Bo-Yi Wu
94aa8dc572 fix: harden log directory creation and path resolution (#61)
- Ensure the log directory is created with secure permissions, falling back to the temp directory if creation fails
- Update log file path to use the resolved log directory

fix https://gitea.com/gitea/gitea-mcp/issues/58

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

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/61
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-committed-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2025-06-13 19:30:14 +00:00
appleboy
05194ffc1c chore: add live reload config and update editor and git settings (#57)
- Add .air.toml configuration file for Air live reloading with specific build and file watch settings
- Ignore the tmp directory in .gitignore
- Rename the gitea server configuration to gitea-mcp-stdio in the VSCode config and add separate configuration for gitea-mcp-http

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

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/57
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2025-06-08 03:55:10 +00:00
appleboy
5c329129f8 docs: standardize server configuration naming in documentation (#56)
- Rename the example "github" server configuration to "gitea-mcp" in all README files

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

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/56
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2025-06-08 02:51:32 +00:00
natchanonnn
52ccf92761 Add edit issue comment and list issue comments tools (#48)
- Add tools:
  - `edit_issue_comment` for edit issue comments
  - `get_issue_comments_by_index` for getting issue's comment by its index

Co-authored-by: hiifong <i@hiif.ong>
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/48
Co-authored-by: natchanonnn <natchanon.w@loolootech.com>
Co-committed-by: natchanonnn <natchanon.w@loolootech.com>
2025-06-03 10:24:50 +00:00
ZRE
061ea86b0b feat: add GetDirContent tool for retrieving directory entries (#53)
### 🚀 What's Changed
This PR introduces a new MCP tool `get_dir_content` that allows users to retrieve a list of entries (files and subdirectories) from a specified directory in a Gitea repository.

###  Features Added
- **New Tool**: `GetDirContent` tool for directory listing functionality
- **Tool Registration**: Properly registered as a read operation in the MCP server
- **Parameter Validation**: Comprehensive input validation for required parameters
- **Error Handling**: Robust error handling with descriptive error messages

### 🔧 Technical Details
- **Tool Name**: `get_dir_content`
- **Required Parameters**:
  - `owner`: Repository owner
  - `repo`: Repository name
  - `ref`: Branch, tag, or commit reference
  - `filePath`: Directory path to list

### 📁 Files Modified
- file.go: Added tool definition, registration, and handler function

### 🎯 Use Cases
This tool enables users to:
- Browse repository directory structures
- List files and folders in specific directories
- Navigate repository contents programmatically
- Support file management workflows in MCP clients

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/53
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: ZRE <chy853@gmail.com>
Co-committed-by: ZRE <chy853@gmail.com>
2025-05-31 19:37:11 +00:00
appleboy
f14b60fe56 build: update base image to distroless/static-debian12:nonroot (#52)
- Update base image from distroless/static-debian11:nonroot to distroless/static-debian12:nonroot

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

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/52
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2025-05-30 07:47:50 +00:00
appleboy
94782a85b6 build: streamline container configuration and metadata (#51)
- Remove the container healthcheck definition
- Delete the image authors label from the build

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

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/51
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2025-05-30 06:47:28 +00:00
appleboy
e94dd26b30 build: refactor Dockerfile for security, performance, and flexibility (#50)
- Switch build base image to Alpine and set platform dynamically
- Use distroless nonroot image for final stage to enhance security
- Add build arguments for VERSION, TARGETOS, and TARGETARCH with defaults
- Cache Go module and build dependencies to improve build performance
- Remove manual installation of ca-certificates and user creation (handled by base image)
- Set nonroot user for running the application
- Add healthcheck for the built binary
- Add OCI-compliant author and version labels

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

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/50
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2025-05-30 04:58:24 +00:00
appleboy
da49bdeb96 feat: integrate server recovery middleware into MCP server initialization (#49)
- Add server recovery middleware to the MCP server initialization

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

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/49
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2025-05-30 04:21:12 +00:00
appleboy
3f61299f72 refactor: refactor HTTP client setup to enhance configuration flexibility (#47)
- Refactor HTTP client initialization to always create a custom http.Client
- Move TLS config modification into the default HTTP client when insecure flag is set
- Ensure the HTTP client is always included in client options

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

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/47
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2025-05-27 12:52:19 +00:00
appleboy
5308fbfb2b docs: add Table of Contents to all README translations (#46)
- Add a Table of Contents section to the README files in English, Simplified Chinese, and Traditional Chinese for improved navigation.

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

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/46
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2025-05-27 12:32:27 +00:00
Hubert Wawrzyńczyk
a7061f9b64 fix: make API bool parameters in search_repos and list_releases optional (#40) (#44)
Fix #40

Left the `mcp.DefaultBool(false)` for `is_draft` and `is_pre_release` in `list_releases`, because I guess they are default, but it's up to the client whether to set them or not.
11e04b5b8d/operation/repo/release.go (L67-L68)

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/44
Reviewed-by: Bo-Yi Wu (吳柏毅) <appleboy.tw@gmail.com>
Co-authored-by: Hubert Wawrzyńczyk <hubert@fit-it.pl>
Co-committed-by: Hubert Wawrzyńczyk <hubert@fit-it.pl>
2025-05-27 12:20:47 +00:00
appleboy
f25cc0de8c feat: add HTTP server mode with updated docs and localization (#45)
- Update download instructions for clarity and consistency in all README files
- Add example configuration for HTTP mode to all README files
- Expand transport type support to include "http" in command-line flags and documentation
- Implement HTTP server mode in the application entrypoint
- Update log output behavior to include "http" mode alongside "sse" for stdout logging
- Refine Chinese README translations for greater accuracy and localization

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

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/45
Co-authored-by: appleboy <appleboy.tw@gmail.com>
Co-committed-by: appleboy <appleboy.tw@gmail.com>
2025-05-27 12:17:37 +00:00
Bo-Yi Wu
417ef26da0 build: add VS Code server config and enable versioning of settings (#43)
- Remove .vscode directory from .gitignore to allow versioning of VS Code settings
- Add a VS Code server configuration file with prompts for Gitea host, access token, and insecure connection option
- Configure a stdio-based server launch for gitea-mcp with relevant environment variables

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

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/43
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-committed-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2025-05-27 04:15:22 +00:00
Bo-Yi Wu
34ca5d45db refactor(args): request argument access and update dependencies (#42)
- Update dependencies to newer versions in go.mod
- Refactor all request argument accesses to use req.GetArguments() instead of direct access to req.Params.Arguments
- Change variable declaration for ListRepoCommitsTool from a grouped var block to a single var statement for consistency

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

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/42
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-committed-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2025-05-26 06:10:10 +00:00
Bo-Yi Wu
796fd4682d docs: document get_user_orgs tool in Chinese guides (#41)
- Add get_user_orgs tool to the list of supported tools in both Simplified and Traditional Chinese documentation

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

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/41
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
Co-committed-by: Bo-Yi Wu <appleboy.tw@gmail.com>
2025-05-26 06:01:59 +00:00
26 changed files with 706 additions and 347 deletions

52
.air.toml Normal file
View File

@@ -0,0 +1,52 @@
root = "."
testdata_dir = "testdata"
tmp_dir = "tmp"
[build]
args_bin = ["-t", "http"]
bin = "./gitea-mcp"
cmd = "make build"
delay = 1000
exclude_dir = ["assets", "tmp", "vendor", "testdata"]
exclude_file = []
exclude_regex = ["_test.go"]
exclude_unchanged = false
follow_symlink = false
full_bin = ""
include_dir = []
include_ext = ["go", "tpl", "tmpl", "html"]
include_file = []
kill_delay = "0s"
log = "build-errors.log"
poll = false
poll_interval = 0
post_cmd = []
pre_cmd = []
rerun = false
rerun_delay = 500
send_interrupt = false
stop_on_error = false
[color]
app = ""
build = "yellow"
main = "magenta"
runner = "green"
watcher = "cyan"
[log]
main_only = false
silent = false
time = false
[misc]
clean_on_exit = false
[proxy]
app_port = 0
enabled = false
proxy_port = 0
[screen]
clear_on_rebuild = false
keep_scroll = true

View File

@@ -24,3 +24,18 @@ jobs:
with: with:
go-version-file: 'go.mod' go-version-file: 'go.mod'
go-package: ./... go-package: ./...
code-scan:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Run Trivy vulnerability scanner in repo mode
uses: aquasecurity/trivy-action@0.28.0
with:
scan-type: 'fs'
ignore-unfixed: true
format: 'sarif'
output: 'trivy-results.sarif'
exit-code: '1'
severity: 'CRITICAL,HIGH'

4
.gitignore vendored
View File

@@ -1,7 +1,5 @@
.idea .idea
.vscode
gitea-mcp gitea-mcp
gitea-mcp.exe gitea-mcp.exe
*.log *.log
tmp

39
.vscode/mcp.json vendored Normal file
View File

@@ -0,0 +1,39 @@
{
// 💡 Inputs are prompted on first server start, then stored securely by VS Code.
"inputs": [
{
"type": "promptString",
"id": "gitea-host",
"description": "Gitea Host",
"password": false
},
{
"type": "promptString",
"id": "gitea-token",
"description": "Gitea Access Token",
"password": true
},
{
"type": "promptString",
"id": "gitea-insecure",
"description": "Allow insecure connections (e.g., self-signed certificates)",
"default": "false"
}
],
"servers": {
"gitea-mcp-stdio": {
"type": "stdio",
"command": "gitea-mcp",
"args": ["-t", "stdio"],
"env": {
"GITEA_HOST": "${input:gitea-host}",
"GITEA_ACCESS_TOKEN": "${input:gitea-token}",
"GITEA_INSECURE": "${input:gitea-insecure}"
}
},
"gitea-mcp-http": {
"type": "http",
"url": "http://localhost:8080/mcp",
}
}
}

View File

@@ -1,39 +1,32 @@
# syntax=docker/dockerfile:1.4
# Build stage # Build stage
FROM golang:1.24-bullseye AS builder FROM --platform=$BUILDPLATFORM golang:1.24-alpine AS builder
ARG VERSION ARG VERSION=dev
ARG TARGETOS
ARG TARGETARCH
# Set the working directory
WORKDIR /app WORKDIR /app
# Copy go.mod and go.sum files
COPY go.mod go.sum ./ COPY go.mod go.sum ./
RUN --mount=type=cache,target=/go/pkg/mod \
go mod download
# Download dependencies
RUN go mod download
# Copy the source code
COPY . . COPY . .
RUN --mount=type=cache,target=/go/pkg/mod \
RUN CGO_ENABLED=0 go build -ldflags="-s -w -X main.Version=${VERSION}" -o gitea-mcp --mount=type=cache,target=/root/.cache/go-build \
CGO_ENABLED=0 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH:-amd64} \
go build -trimpath -ldflags="-s -w -X main.Version=${VERSION}" -o gitea-mcp
# Final stage # Final stage
FROM debian:bullseye-slim FROM gcr.io/distroless/static-debian12:nonroot
ENV GITEA_MODE=stdio
WORKDIR /app WORKDIR /app
COPY --from=builder --chown=nonroot:nonroot /app/gitea-mcp .
# Install ca-certificates for HTTPS requests USER nonroot:nonroot
RUN apt-get update && \
apt-get install -y ca-certificates && rm -rf /var/lib/apt/lists/*
# Create a non-root user LABEL org.opencontainers.image.version="${VERSION}"
RUN useradd -r -u 1000 -m gitea-mcp
COPY --from=builder --chown=1000:1000 /app/gitea-mcp .
# Use the non-root user
USER gitea-mcp
CMD ["/app/gitea-mcp"] CMD ["/app/gitea-mcp"]

107
README.md
View File

@@ -6,6 +6,22 @@
[![Install with Docker in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}) [![Install with Docker in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install_Server-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}&quality=insiders) [![Install with Docker in VS Code](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}) [![Install with Docker in VS Code Insiders](https://img.shields.io/badge/VS_Code_Insiders-Install_Server-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}&quality=insiders)
## Table of Contents
- [Gitea MCP Server](#gitea-mcp-server)
- [Table of Contents](#table-of-contents)
- [What is Gitea?](#what-is-gitea)
- [What is MCP?](#what-is-mcp)
- [🚧 Installation](#-installation)
- [Usage with VS Code](#usage-with-vs-code)
- [📥 Download the official binary release](#-download-the-official-binary-release)
- [🔧 Build from Source](#-build-from-source)
- [📁 Add to PATH](#-add-to-path)
- [🚀 Usage](#-usage)
- [✅ Available Tools](#-available-tools)
- [🐛 Debugging](#-debugging)
- [🛠 Troubleshooting](#-troubleshooting)
## What is Gitea? ## What is Gitea?
Gitea is a community-managed lightweight code hosting solution written in Go. It is published under the MIT license. Gitea provides Git hosting including a repository viewer, issue tracking, pull requests, and more. Gitea is a community-managed lightweight code hosting solution written in Go. It is published under the MIT license. Gitea provides Git hosting including a repository viewer, issue tracking, pull requests, and more.
@@ -38,7 +54,7 @@ Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace
} }
], ],
"servers": { "servers": {
"github": { "gitea-mcp": {
"command": "docker", "command": "docker",
"args": [ "args": [
"run", "run",
@@ -59,7 +75,7 @@ Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace
### 📥 Download the official binary release ### 📥 Download the official binary release
You can download the official release from [here](https://gitea.com/gitea/gitea-mcp/releases). You can download the official release from [official Gitea MCP binary releases](https://gitea.com/gitea/gitea-mcp/releases).
### 🔧 Build from Source ### 🔧 Build from Source
@@ -129,6 +145,18 @@ To configure the MCP server for Gitea, add the following to your MCP configurati
} }
``` ```
- **http mode**
```json
{
"mcpServers": {
"gitea": {
"url": "http://localhost:8080/mcp"
}
}
}
```
**Default log path**: `$HOME/.gitea-mcp/gitea-mcp.log` **Default log path**: `$HOME/.gitea-mcp/gitea-mcp.log`
> [!NOTE] > [!NOTE]
@@ -145,42 +173,45 @@ list all my repositories
The Gitea MCP Server supports the following tools: The Gitea MCP Server supports the following tools:
| Tool | Scope | Description | | Tool | Scope | Description |
| :--------------------------: | :----------: | :---------------------------------------------------: | | :--------------------------: | :----------: | :------------------------------------------------------: |
| get_my_user_info | User | Get the information of the authenticated user | | get_my_user_info | User | Get the information of the authenticated user |
| get_user_orgs | User | Get organizations associated with the authenticated user | | get_user_orgs | User | Get organizations associated with the authenticated user |
| create_repo | Repository | Create a new repository | | create_repo | Repository | Create a new repository |
| fork_repo | Repository | Fork a repository | | fork_repo | Repository | Fork a repository |
| list_my_repos | Repository | List all repositories owned by the authenticated user | | list_my_repos | Repository | List all repositories owned by the authenticated user |
| create_branch | Branch | Create a new branch | | create_branch | Branch | Create a new branch |
| delete_branch | Branch | Delete a branch | | delete_branch | Branch | Delete a branch |
| list_branches | Branch | List all branches in a repository | | list_branches | Branch | List all branches in a repository |
| create_release | Release | Create a new release in a repository | | create_release | Release | Create a new release in a repository |
| delete_release | Release | Delete a release from a repository | | delete_release | Release | Delete a release from a repository |
| get_release | Release | Get a release | | get_release | Release | Get a release |
| get_latest_release | Release | Get the latest release in a repository | | get_latest_release | Release | Get the latest release in a repository |
| list_releases | Release | List all releases in a repository | | list_releases | Release | List all releases in a repository |
| create_tag | Tag | Create a new tag | | create_tag | Tag | Create a new tag |
| delete_tag | Tag | Delete a tag | | delete_tag | Tag | Delete a tag |
| get_tag | Tag | Get a tag | | get_tag | Tag | Get a tag |
| list_tags | Tag | List all tags in a repository | | list_tags | Tag | List all tags in a repository |
| list_repo_commits | Commit | List all commits in a repository | | list_repo_commits | Commit | List all commits in a repository |
| get_file_content | File | Get the content and metadata of a file | | get_file_content | File | Get the content and metadata of a file |
| create_file | File | Create a new file | | get_dir_content | File | Get a list of entries in a directory |
| update_file | File | Update an existing file | | create_file | File | Create a new file |
| delete_file | File | Delete a file | | update_file | File | Update an existing file |
| get_issue_by_index | Issue | Get an issue by its index | | delete_file | File | Delete a file |
| list_repo_issues | Issue | List all issues in a repository | | get_issue_by_index | Issue | Get an issue by its index |
| create_issue | Issue | Create a new issue | | list_repo_issues | Issue | List all issues in a repository |
| create_issue_comment | Issue | Create a comment on an issue | | create_issue | Issue | Create a new issue |
| edit_issue | Issue | Edit a issue | | create_issue_comment | Issue | Create a comment on an issue |
| get_pull_request_by_index | Pull Request | Get a pull request by its index | | edit_issue | Issue | Edit a issue |
| list_repo_pull_requests | Pull Request | List all pull requests in a repository | | edit_issue_comment | Issue | Edit a comment on an issue |
| create_pull_request | Pull Request | Create a new pull request | | get_issue_comments_by_index | Issue | Get comments of an issue by its index |
| search_users | User | Search for users | | get_pull_request_by_index | Pull Request | Get a pull request by its index |
| search_org_teams | Organization | Search for teams in an organization | | list_repo_pull_requests | Pull Request | List all pull requests in a repository |
| search_repos | Repository | Search for repositories | | create_pull_request | Pull Request | Create a new pull request |
| get_gitea_mcp_server_version | Server | Get the version of the Gitea MCP Server | | search_users | User | Search for users |
| search_org_teams | Organization | Search for teams in an organization |
| search_repos | Repository | Search for repositories |
| get_gitea_mcp_server_version | Server | Get the version of the Gitea MCP Server |
## 🐛 Debugging ## 🐛 Debugging

View File

@@ -6,6 +6,22 @@
[![在 VS Code 中使用 Docker 安装](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}) [![在 VS Code Insiders 中使用 Docker 安装](https://img.shields.io/badge/VS_Code_Insiders-Install_Server-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}&quality=insiders) [![在 VS Code 中使用 Docker 安装](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}) [![在 VS Code Insiders 中使用 Docker 安装](https://img.shields.io/badge/VS_Code_Insiders-Install_Server-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}&quality=insiders)
## 目录
- [Gitea MCP 服务器](#gitea-mcp-服务器)
- [目录](#目录)
- [什么是 Gitea](#什么是-gitea)
- [什么是 MCP](#什么是-mcp)
- [🚧 安装](#-安装)
- [在 VS Code 中使用](#在-vs-code-中使用)
- [📥 下载官方 Gitea MCP 二进制版本](#-下载官方-gitea-mcp-二进制版本)
- [🔧 从源代码构建](#-从源代码构建)
- [📁 添加到 PATH](#-添加到-path)
- [🚀 使用](#-使用)
- [✅ 可用工具](#-可用工具)
- [🐛 调试](#-调试)
- [🛠 疑难排解](#-疑难排解)
## 什么是 Gitea ## 什么是 Gitea
Gitea 是一个由社区管理的轻量级代码托管解决方案,使用 Go 语言编写。它以 MIT 许可证发布。Gitea 提供 Git 托管,包括仓库查看器、问题追踪、拉取请求等功能。 Gitea 是一个由社区管理的轻量级代码托管解决方案,使用 Go 语言编写。它以 MIT 许可证发布。Gitea 提供 Git 托管,包括仓库查看器、问题追踪、拉取请求等功能。
@@ -38,7 +54,7 @@ Model Context Protocol (MCP) 是一种协议,允许通过聊天界面整合各
} }
], ],
"servers": { "servers": {
"github": { "gitea-mcp": {
"command": "docker", "command": "docker",
"args": [ "args": [
"run", "run",
@@ -57,9 +73,9 @@ Model Context Protocol (MCP) 是一种协议,允许通过聊天界面整合各
} }
``` ```
### 📥 下载官方二进制版本 ### 📥 下载官方 Gitea MCP 二进制版本
您可以从[这里](https://gitea.com/gitea/gitea-mcp/releases)下载官方版本。 您可以从[官方 Gitea MCP 二进制版本](https://gitea.com/gitea/gitea-mcp/releases)下载官方版本。
### 🔧 从源代码构建 ### 🔧 从源代码构建
@@ -129,6 +145,18 @@ cp gitea-mcp /usr/local/bin/
} }
``` ```
- **http 模式**
```json
{
"mcpServers": {
"gitea": {
"url": "http://localhost:8080/mcp"
}
}
}
```
**默认日志路径**: `$HOME/.gitea-mcp/gitea-mcp.log` **默认日志路径**: `$HOME/.gitea-mcp/gitea-mcp.log`
> [!注意] > [!注意]
@@ -148,23 +176,25 @@ Gitea MCP 服务器支持以下工具:
| 工具 | 范围 | 描述 | | 工具 | 范围 | 描述 |
| :--------------------------: | :------: | :--------------------------: | | :--------------------------: | :------: | :--------------------------: |
| get_my_user_info | 用户 | 获取已认证用户的信息 | | get_my_user_info | 用户 | 获取已认证用户的信息 |
| get_user_orgs | 用户 | 获取已认证用户关联的组织 |
| create_repo | 仓库 | 创建一个新仓库 | | create_repo | 仓库 | 创建一个新仓库 |
| fork_repo | 仓库 | 复刻一个仓库 | | fork_repo | 仓库 | 复刻一个仓库 |
| list_my_repos | 仓库 | 列出已认证用户拥有的所有仓库 | | list_my_repos | 仓库 | 列出已认证用户拥有的所有仓库 |
| create_branch | 分支 | 创建一个新分支 | | create_branch | 分支 | 创建一个新分支 |
| delete_branch | 分支 | 删除一个分支 | | delete_branch | 分支 | 删除一个分支 |
| list_branches | 分支 | 列出仓库中的所有分支 | | list_branches | 分支 | 列出仓库中的所有分支 |
| create_release | 版本发布 | 创建一个新版本发布 | | create_release | 版本发布 | 创建一个新版本发布 |
| delete_release | 版本发布 | 删除一个版本发布 | | delete_release | 版本发布 | 删除一个版本发布 |
| get_release | 版本发布 | 获取一个版本发布 | | get_release | 版本发布 | 获取一个版本发布 |
| get_latest_release | 版本发布 | 获取最新的版本发布 | | get_latest_release | 版本发布 | 获取最新的版本发布 |
| list_releases | 版本发布 | 列出所有版本发布 | | list_releases | 版本发布 | 列出所有版本发布 |
| create_tag | 标签 | 创建一个新标签 | | create_tag | 标签 | 创建一个新标签 |
| delete_tag | 标签 | 删除一个标签 | | delete_tag | 标签 | 删除一个标签 |
| get_tag | 标签 | 获取一个标签 | | get_tag | 标签 | 获取一个标签 |
| list_tags | 标签 | 列出所有标签 | | list_tags | 标签 | 列出所有标签 |
| list_repo_commits | 提交 | 列出仓库中的所有提交 | | list_repo_commits | 提交 | 列出仓库中的所有提交 |
| get_file_content | 文件 | 获取文件的内容和元数据 | | get_file_content | 文件 | 获取文件的内容和元数据 |
| get_dir_content | 文件 | 获取目录的内容列表 |
| create_file | 文件 | 创建一个新文件 | | create_file | 文件 | 创建一个新文件 |
| update_file | 文件 | 更新现有文件 | | update_file | 文件 | 更新现有文件 |
| delete_file | 文件 | 删除一个文件 | | delete_file | 文件 | 删除一个文件 |
@@ -172,14 +202,16 @@ Gitea MCP 服务器支持以下工具:
| list_repo_issues | 问题 | 列出仓库中的所有问题 | | list_repo_issues | 问题 | 列出仓库中的所有问题 |
| create_issue | 问题 | 创建一个新问题 | | create_issue | 问题 | 创建一个新问题 |
| create_issue_comment | 问题 | 在问题上创建评论 | | create_issue_comment | 问题 | 在问题上创建评论 |
| edit_issue | 问题 | 编辑一个问题 | | edit_issue | 问题 | 编辑一个问题 |
| edit_issue_comment | 问题 | 在问题上编辑评论 |
| get_issue_comments_by_index | 问题 | 根据索引获取问题的评论 |
| get_pull_request_by_index | 拉取请求 | 根据索引获取拉取请求 | | get_pull_request_by_index | 拉取请求 | 根据索引获取拉取请求 |
| list_repo_pull_requests | 拉取请求 | 列出仓库中的所有拉取请求 | | list_repo_pull_requests | 拉取请求 | 列出仓库中的所有拉取请求 |
| create_pull_request | 拉取请求 | 创建一个新拉取请求 | | create_pull_request | 拉取请求 | 创建一个新拉取请求 |
| search_users | 用户 | 搜索用户 | | search_users | 用户 | 搜索用户 |
| search_org_teams | 组织 | 搜索组织中的团队 | | search_org_teams | 组织 | 搜索组织中的团队 |
| search_repos | 仓库 | 搜索仓库 | | search_repos | 仓库 | 搜索仓库 |
| get_gitea_mcp_server_version | 服务器 | 获取 Gitea MCP 服务器的版本 | | get_gitea_mcp_server_version | 服务器 | 获取 Gitea MCP 服务器的版本 |
## 🐛 调试 ## 🐛 调试

View File

@@ -6,6 +6,22 @@
[![在 VS Code 中使用 Docker 安裝](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}) [![在 VS Code Insiders 中使用 Docker 安裝](https://img.shields.io/badge/VS_Code_Insiders-Install_Server-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}&quality=insiders) [![在 VS Code 中使用 Docker 安裝](https://img.shields.io/badge/VS_Code-Install_Server-0098FF?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}) [![在 VS Code Insiders 中使用 Docker 安裝](https://img.shields.io/badge/VS_Code_Insiders-Install_Server-24bfa5?style=flat-square&logo=visualstudiocode&logoColor=white)](https://insiders.vscode.dev/redirect/mcp/install?name=gitea&inputs=[{%22id%22:%22gitea_token%22,%22type%22:%22promptString%22,%22description%22:%22Gitea%20Personal%20Access%20Token%22,%22password%22:true}]&config={%22command%22:%22docker%22,%22args%22:[%22run%22,%22-i%22,%22--rm%22,%22-e%22,%22GITEA_ACCESS_TOKEN%22,%22docker.gitea.com/gitea-mcp-server%22],%22env%22:{%22GITEA_ACCESS_TOKEN%22:%22${input:gitea_token}%22}}&quality=insiders)
## 目錄
- [Gitea MCP 伺服器](#gitea-mcp-伺服器)
- [目錄](#目錄)
- [什麼是 Gitea](#什麼是-gitea)
- [什麼是 MCP](#什麼是-mcp)
- [🚧 安裝](#-安裝)
- [在 VS Code 中使用](#在-vs-code-中使用)
- [📥 下載官方 Gitea MCP 二進位版本](#-下載官方-gitea-mcp-二進位版本)
- [🔧 從源代碼構建](#-從源代碼構建)
- [📁 添加到 PATH](#-添加到-path)
- [🚀 使用](#-使用)
- [✅ 可用工具](#-可用工具)
- [🐛 調試](#-調試)
- [🛠 疑難排解](#-疑難排解)
## 什麼是 Gitea ## 什麼是 Gitea
Gitea 是一個由社群管理的輕量級代碼託管解決方案,使用 Go 語言編寫。它以 MIT 許可證發布。Gitea 提供 Git 託管,包括倉庫查看器、問題追蹤、拉取請求等功能。 Gitea 是一個由社群管理的輕量級代碼託管解決方案,使用 Go 語言編寫。它以 MIT 許可證發布。Gitea 提供 Git 託管,包括倉庫查看器、問題追蹤、拉取請求等功能。
@@ -38,7 +54,7 @@ Model Context Protocol (MCP) 是一種協議,允許通過聊天界面整合各
} }
], ],
"servers": { "servers": {
"github": { "gitea-mcp": {
"command": "docker", "command": "docker",
"args": [ "args": [
"run", "run",
@@ -57,9 +73,9 @@ Model Context Protocol (MCP) 是一種協議,允許通過聊天界面整合各
} }
``` ```
### 📥 下載官方二進版本 ### 📥 下載官方 Gitea MCP 二進版本
您可以從[這裡](https://gitea.com/gitea/gitea-mcp/releases)下載官方版本。 您可以從[官方 Gitea MCP 二進位版本](https://gitea.com/gitea/gitea-mcp/releases)下載官方版本。
### 🔧 從源代碼構建 ### 🔧 從源代碼構建
@@ -129,11 +145,23 @@ cp gitea-mcp /usr/local/bin/
} }
``` ```
**默認日誌路徑**: `$HOME/.gitea-mcp/gitea-mcp.log` - **http 模式**
```json
{
"mcpServers": {
"gitea": {
"url": "http://localhost:8080/mcp"
}
}
}
```
**預設日誌路徑**: `$HOME/.gitea-mcp/gitea-mcp.log`
> [!注意] > [!注意]
> 您可以通過命令參數或環境變提供您的 Gitea 主機和訪問令牌。 > 您可以通過命令參數或環境變提供您的 Gitea 主機和訪問令牌。
> 命令參數具有最高優先 > 命令參數具有最高優先
一切設置完成後,請嘗試在您的 MCP 兼容聊天框中輸入以下內容: 一切設置完成後,請嘗試在您的 MCP 兼容聊天框中輸入以下內容:
@@ -144,26 +172,29 @@ cp gitea-mcp /usr/local/bin/
## ✅ 可用工具 ## ✅ 可用工具
Gitea MCP 伺服器支持以下工具: Gitea MCP 伺服器支持以下工具:
| 工具 | 範圍 | 描述 | | 工具 | 範圍 | 描述 |
| :--------------------------: | :------: | :--------------------------: | | :--------------------------: | :------: | :--------------------------: |
| get_my_user_info | 用戶 | 獲取已認證用戶的信息 | | get_my_user_info | 用戶 | 獲取已認證用戶的信息 |
| get_user_orgs | 用戶 | 取得已認證用戶所屬組織 |
| create_repo | 倉庫 | 創建一個新倉庫 | | create_repo | 倉庫 | 創建一個新倉庫 |
| fork_repo | 倉庫 | 復刻一個倉庫 | | fork_repo | 倉庫 | 復刻一個倉庫 |
| list_my_repos | 倉庫 | 列出已認證用戶擁有的所有倉庫 | | list_my_repos | 倉庫 | 列出已認證用戶擁有的所有倉庫 |
| create_branch | 分支 | 創建一個新分支 | | create_branch | 分支 | 創建一個新分支 |
| delete_branch | 分支 | 刪除一個分支 | | delete_branch | 分支 | 刪除一個分支 |
| list_branches | 分支 | 列出倉庫中的所有分支 | | list_branches | 分支 | 列出倉庫中的所有分支 |
| create_release | 版本發布 | 創建一個新版本發布 | | create_release | 版本發布 | 創建一個新版本發布 |
| delete_release | 版本發布 | 刪除一個版本發布 | | delete_release | 版本發布 | 刪除一個版本發布 |
| get_release | 版本發布 | 獲取一個版本發布 | | get_release | 版本發布 | 獲取一個版本發布 |
| get_latest_release | 版本發布 | 獲取最新的版本發布 | | get_latest_release | 版本發布 | 獲取最新的版本發布 |
| list_releases | 版本發布 | 列出所有版本發布 | | list_releases | 版本發布 | 列出所有版本發布 |
| create_tag | 標籤 | 創建一個新標籤 | | create_tag | 標籤 | 創建一個新標籤 |
| delete_tag | 標籤 | 刪除一個標籤 | | delete_tag | 標籤 | 刪除一個標籤 |
| get_tag | 標籤 | 獲取一個標籤 | | get_tag | 標籤 | 獲取一個標籤 |
| list_tags | 標籤 | 列出所有標籤 | | list_tags | 標籤 | 列出所有標籤 |
| list_repo_commits | 提交 | 列出倉庫中的所有提交 | | list_repo_commits | 提交 | 列出倉庫中的所有提交 |
| get_file_content | 文件 | 獲取文件的內容和元數據 | | get_file_content | 文件 | 獲取文件的內容和元數據 |
| get_dir_content | 文件 | 獲取目錄的內容列表 |
| create_file | 文件 | 創建一個新文件 | | create_file | 文件 | 創建一個新文件 |
| update_file | 文件 | 更新現有文件 | | update_file | 文件 | 更新現有文件 |
| delete_file | 文件 | 刪除一個文件 | | delete_file | 文件 | 刪除一個文件 |
@@ -171,18 +202,20 @@ Gitea MCP 伺服器支持以下工具:
| list_repo_issues | 問題 | 列出倉庫中的所有問題 | | list_repo_issues | 問題 | 列出倉庫中的所有問題 |
| create_issue | 問題 | 創建一個新問題 | | create_issue | 問題 | 創建一個新問題 |
| create_issue_comment | 問題 | 在問題上創建評論 | | create_issue_comment | 問題 | 在問題上創建評論 |
| edit_issue | 問題 | 編輯一個問題 | | edit_issue | 問題 | 編輯一個問題 |
| edit_issue_comment | 問題 | 在問題上編輯評論 |
| get_issue_comments_by_index | 问题 | 根據索引獲取問題的評論 |
| get_pull_request_by_index | 拉取請求 | 根據索引獲取拉取請求 | | get_pull_request_by_index | 拉取請求 | 根據索引獲取拉取請求 |
| list_repo_pull_requests | 拉取請求 | 列出倉庫中的所有拉取請求 | | list_repo_pull_requests | 拉取請求 | 列出倉庫中的所有拉取請求 |
| create_pull_request | 拉取請求 | 創建一個新拉取請求 | | create_pull_request | 拉取請求 | 創建一個新拉取請求 |
| search_users | 用戶 | 搜索用戶 | | search_users | 用戶 | 搜索用戶 |
| search_org_teams | 組織 | 搜索組織中的團隊 | | search_org_teams | 組織 | 搜索組織中的團隊 |
| search_repos | 倉庫 | 搜索倉庫 | | search_repos | 倉庫 | 搜索倉庫 |
| get_gitea_mcp_server_version | 伺服器 | 獲取 Gitea MCP 伺服器的版本 | | get_gitea_mcp_server_version | 伺服器 | 獲取 Gitea MCP 伺服器的版本 |
## 🐛 調試 ## 🐛 調試
要啟用調試模式,請在使用 sse 模式運行 Gitea MCP 伺服器時添加 `-d` 要啟用調試模式,請在使用 sse 模式運行 Gitea MCP 伺服器時添加 `-d` 標:
```sh ```sh
./gitea-mcp -t sse [--port 8080] --token <your personal access token> -d ./gitea-mcp -t sse [--port 8080] --token <your personal access token> -d

View File

@@ -21,13 +21,13 @@ func init() {
&flagPkg.Mode, &flagPkg.Mode,
"t", "t",
"stdio", "stdio",
"Transport type (stdio or sse)", "Transport type (stdio, sse or http)",
) )
flag.StringVar( flag.StringVar(
&flagPkg.Mode, &flagPkg.Mode,
"transport", "transport",
"stdio", "stdio",
"Transport type (stdio or sse)", "Transport type (stdio, sse or http)",
) )
flag.StringVar( flag.StringVar(
&host, &host,
@@ -39,7 +39,7 @@ func init() {
&port, &port,
"port", "port",
8080, 8080,
"sse port", "see or http port",
) )
flag.StringVar( flag.StringVar(
&token, &token,
@@ -80,8 +80,8 @@ func init() {
flagPkg.Token = os.Getenv("GITEA_ACCESS_TOKEN") flagPkg.Token = os.Getenv("GITEA_ACCESS_TOKEN")
} }
if os.Getenv("GITEA_MODE") != "" { if os.Getenv("MCP_MODE") != "" {
flagPkg.Mode = os.Getenv("GITEA_MODE") flagPkg.Mode = os.Getenv("MCP_MODE")
} }
if os.Getenv("GITEA_READONLY") == "true" { if os.Getenv("GITEA_READONLY") == "true" {

10
go.mod
View File

@@ -4,20 +4,20 @@ go 1.24.0
require ( require (
code.gitea.io/sdk/gitea v0.21.0 code.gitea.io/sdk/gitea v0.21.0
github.com/mark3labs/mcp-go v0.22.0 github.com/mark3labs/mcp-go v0.35.0
go.uber.org/zap v1.27.0 go.uber.org/zap v1.27.0
gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/natefinch/lumberjack.v2 v2.2.1
) )
require ( require (
github.com/42wim/httpsig v1.2.2 // indirect github.com/42wim/httpsig v1.2.3 // indirect
github.com/davidmz/go-pageant v1.0.2 // indirect github.com/davidmz/go-pageant v1.0.2 // indirect
github.com/go-fed/httpsig v1.1.0 // indirect github.com/go-fed/httpsig v1.1.0 // indirect
github.com/google/uuid v1.6.0 // indirect github.com/google/uuid v1.6.0 // indirect
github.com/hashicorp/go-version v1.7.0 // indirect github.com/hashicorp/go-version v1.7.0 // indirect
github.com/spf13/cast v1.7.1 // indirect github.com/spf13/cast v1.9.2 // indirect
github.com/yosida95/uritemplate/v3 v3.0.2 // indirect github.com/yosida95/uritemplate/v3 v3.0.2 // indirect
go.uber.org/multierr v1.11.0 // indirect go.uber.org/multierr v1.11.0 // indirect
golang.org/x/crypto v0.37.0 // indirect golang.org/x/crypto v0.40.0 // indirect
golang.org/x/sys v0.32.0 // indirect golang.org/x/sys v0.34.0 // indirect
) )

24
go.sum
View File

@@ -1,7 +1,7 @@
code.gitea.io/sdk/gitea v0.21.0 h1:69n6oz6kEVHRo1+APQQyizkhrZrLsTLXey9142pfkD4= code.gitea.io/sdk/gitea v0.21.0 h1:69n6oz6kEVHRo1+APQQyizkhrZrLsTLXey9142pfkD4=
code.gitea.io/sdk/gitea v0.21.0/go.mod h1:tnBjVhuKJCn8ibdyyhvUyxrR1Ca2KHEoTWoukNhXQPA= code.gitea.io/sdk/gitea v0.21.0/go.mod h1:tnBjVhuKJCn8ibdyyhvUyxrR1Ca2KHEoTWoukNhXQPA=
github.com/42wim/httpsig v1.2.2 h1:ofAYoHUNs/MJOLqQ8hIxeyz2QxOz8qdSVvp3PX/oPgA= github.com/42wim/httpsig v1.2.3 h1:xb0YyWhkYj57SPtfSttIobJUPJZB9as1nsfo7KWVcEs=
github.com/42wim/httpsig v1.2.2/go.mod h1:P/UYo7ytNBFwc+dg35IubuAUIs8zj5zzFIgUCEl55WY= github.com/42wim/httpsig v1.2.3/go.mod h1:nZq9OlYKDrUBhptd77IHx4/sZZD+IxTBADvAPI9G/EM=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0= github.com/davidmz/go-pageant v1.0.2 h1:bPblRCh5jGU+Uptpz6LgMZGD5hJoOt7otgT454WvHn0=
@@ -20,14 +20,14 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/mark3labs/mcp-go v0.22.0 h1:cCEBWi4Yy9Kio+OW1hWIyi4WLsSr+RBBK6FI5tj+b7I= github.com/mark3labs/mcp-go v0.35.0 h1:eh5bJGGVkNEaehCbPmAFqFgk/SB18YvxmsR2rnPm8BQ=
github.com/mark3labs/mcp-go v0.22.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4= github.com/mark3labs/mcp-go v0.35.0/go.mod h1:rXqOudj/djTORU/ThxYx8fqEVj/5pvTuuebQ2RC7uk4=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8= github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
github.com/spf13/cast v1.7.1 h1:cuNEagBQEHWN1FnbGEjCXL2szYEXqfJPbP2HNUaca9Y= github.com/spf13/cast v1.9.2 h1:SsGfm7M8QOFtEzumm7UZrZdLLquNdzFYfIbEXntcFbE=
github.com/spf13/cast v1.7.1/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo= github.com/spf13/cast v1.9.2/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
@@ -41,18 +41,18 @@ go.uber.org/zap v1.27.0/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8= golang.org/x/crypto v0.0.0-20210513164829-c07d793c2f9a/go.mod h1:P+XmwS30IXTQdn5tA2iutPOUgjI07+tq3H3K9MVA1s8=
golang.org/x/crypto v0.37.0 h1:kJNSjF/Xp7kU0iB2Z+9viTPMW4EqqsrywMXLJOOsXSE= golang.org/x/crypto v0.40.0 h1:r4x+VvoG5Fm+eJcxMaY8CQM7Lb0l1lsmjGBQ6s8BfKM=
golang.org/x/crypto v0.37.0/go.mod h1:vg+k43peMZ0pUMhYmVAWysMK35e6ioLh3wB8ZCAfbVc= golang.org/x/crypto v0.40.0/go.mod h1:Qr1vMER5WyS2dfPHAlsOj01wgLbsyWtFn/aY+5+ZdxY=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.32.0 h1:s77OFDvIQeibCmezSnk/q6iAfkdiQaJi4VzroCFrN20= golang.org/x/sys v0.34.0 h1:H5Y5sJ2L2JRdyv7ROF1he/lPdvFsd0mJHFw2ThKHxLA=
golang.org/x/sys v0.32.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/sys v0.34.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/term v0.31.0 h1:erwDkOK1Msy6offm1mOgvspSkslFnIGsFnxOKoufg3o= golang.org/x/term v0.33.0 h1:NuFncQrRcaRvVmgRkvM3j/F00gWIAlcmlB8ACEKmGIg=
golang.org/x/term v0.31.0/go.mod h1:R4BeIy7D95HzImkxGkTW1UQTtP54tio2RyHz7PwK0aw= golang.org/x/term v0.33.0/go.mod h1:s18+ql9tYWp1IfpV9DmCtQDDSRBUjKaw9M1eAv5UeF0=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=

View File

@@ -18,11 +18,13 @@ import (
var Tool = tool.New() var Tool = tool.New()
const ( const (
GetIssueByIndexToolName = "get_issue_by_index" GetIssueByIndexToolName = "get_issue_by_index"
ListRepoIssuesToolName = "list_repo_issues" ListRepoIssuesToolName = "list_repo_issues"
CreateIssueToolName = "create_issue" CreateIssueToolName = "create_issue"
CreateIssueCommentToolName = "create_issue_comment" CreateIssueCommentToolName = "create_issue_comment"
EditIssueToolName = "edit_issue" EditIssueToolName = "edit_issue"
EditIssueCommentToolName = "edit_issue_comment"
GetIssueCommentsByIndexToolName = "get_issue_comments_by_index"
) )
var ( var (
@@ -52,6 +54,7 @@ var (
mcp.WithString("title", mcp.Required(), mcp.Description("issue title")), mcp.WithString("title", mcp.Required(), mcp.Description("issue title")),
mcp.WithString("body", mcp.Required(), mcp.Description("issue body")), mcp.WithString("body", mcp.Required(), mcp.Description("issue body")),
) )
CreateIssueCommentTool = mcp.NewTool( CreateIssueCommentTool = mcp.NewTool(
CreateIssueCommentToolName, CreateIssueCommentToolName,
mcp.WithDescription("create issue comment"), mcp.WithDescription("create issue comment"),
@@ -60,6 +63,7 @@ var (
mcp.WithNumber("index", mcp.Required(), mcp.Description("repository issue index")), mcp.WithNumber("index", mcp.Required(), mcp.Description("repository issue index")),
mcp.WithString("body", mcp.Required(), mcp.Description("issue comment body")), mcp.WithString("body", mcp.Required(), mcp.Description("issue comment body")),
) )
EditIssueTool = mcp.NewTool( EditIssueTool = mcp.NewTool(
EditIssueToolName, EditIssueToolName,
mcp.WithDescription("edit issue"), mcp.WithDescription("edit issue"),
@@ -72,6 +76,23 @@ var (
mcp.WithNumber("milestone", mcp.Description("milestone number")), mcp.WithNumber("milestone", mcp.Description("milestone number")),
mcp.WithString("state", mcp.Description("issue state, one of open, closed, all")), mcp.WithString("state", mcp.Description("issue state, one of open, closed, all")),
) )
EditIssueCommentTool = mcp.NewTool(
EditIssueCommentToolName,
mcp.WithDescription("edit issue comment"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithNumber("commentID", mcp.Required(), mcp.Description("id of issue comment")),
mcp.WithString("body", mcp.Required(), mcp.Description("issue comment body")),
)
GetIssueCommentsByIndexTool = mcp.NewTool(
GetIssueCommentsByIndexToolName,
mcp.WithDescription("get issue comment by index"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithNumber("index", mcp.Required(), mcp.Description("repository issue index")),
)
) )
func init() { func init() {
@@ -95,19 +116,27 @@ func init() {
Tool: EditIssueTool, Tool: EditIssueTool,
Handler: EditIssueFn, Handler: EditIssueFn,
}) })
Tool.RegisterWrite(server.ServerTool{
Tool: EditIssueCommentTool,
Handler: EditIssueCommentFn,
})
Tool.RegisterRead(server.ServerTool{
Tool: GetIssueCommentsByIndexTool,
Handler: GetIssueCommentsByIndexFn,
})
} }
func GetIssueByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetIssueByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetIssueByIndexFn") log.Debugf("Called GetIssueByIndexFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }
index, ok := req.Params.Arguments["index"].(float64) index, ok := req.GetArguments()["index"].(float64)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("index is required")) return to.ErrorResult(fmt.Errorf("index is required"))
} }
@@ -121,23 +150,23 @@ func GetIssueByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
func ListRepoIssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListRepoIssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListIssuesFn") log.Debugf("Called ListIssuesFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }
state, ok := req.Params.Arguments["state"].(string) state, ok := req.GetArguments()["state"].(string)
if !ok { if !ok {
state = "all" state = "all"
} }
page, ok := req.Params.Arguments["page"].(float64) page, ok := req.GetArguments()["page"].(float64)
if !ok { if !ok {
page = 1 page = 1
} }
pageSize, ok := req.Params.Arguments["pageSize"].(float64) pageSize, ok := req.GetArguments()["pageSize"].(float64)
if !ok { if !ok {
pageSize = 100 pageSize = 100
} }
@@ -157,19 +186,19 @@ func ListRepoIssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
func CreateIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CreateIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateIssueFn") log.Debugf("Called CreateIssueFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }
title, ok := req.Params.Arguments["title"].(string) title, ok := req.GetArguments()["title"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("title is required")) return to.ErrorResult(fmt.Errorf("title is required"))
} }
body, ok := req.Params.Arguments["body"].(string) body, ok := req.GetArguments()["body"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("body is required")) return to.ErrorResult(fmt.Errorf("body is required"))
} }
@@ -186,19 +215,19 @@ func CreateIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
func CreateIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CreateIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateIssueCommentFn") log.Debugf("Called CreateIssueCommentFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }
index, ok := req.Params.Arguments["index"].(float64) index, ok := req.GetArguments()["index"].(float64)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("index is required")) return to.ErrorResult(fmt.Errorf("index is required"))
} }
body, ok := req.Params.Arguments["body"].(string) body, ok := req.GetArguments()["body"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("body is required")) return to.ErrorResult(fmt.Errorf("body is required"))
} }
@@ -215,38 +244,38 @@ func CreateIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca
func EditIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func EditIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called EditIssueFn") log.Debugf("Called EditIssueFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }
index, ok := req.Params.Arguments["index"].(float64) index, ok := req.GetArguments()["index"].(float64)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("index is required")) return to.ErrorResult(fmt.Errorf("index is required"))
} }
opt := gitea_sdk.EditIssueOption{} opt := gitea_sdk.EditIssueOption{}
title, ok := req.Params.Arguments["title"].(string) title, ok := req.GetArguments()["title"].(string)
if ok { if ok {
opt.Title = title opt.Title = title
} }
body, ok := req.Params.Arguments["body"].(string) body, ok := req.GetArguments()["body"].(string)
if ok { if ok {
opt.Body = ptr.To(body) opt.Body = ptr.To(body)
} }
assignees, ok := req.Params.Arguments["assignees"].([]string) assignees, ok := req.GetArguments()["assignees"].([]string)
if ok { if ok {
opt.Assignees = assignees opt.Assignees = assignees
} }
milestone, ok := req.Params.Arguments["milestone"].(float64) milestone, ok := req.GetArguments()["milestone"].(float64)
if ok { if ok {
opt.Milestone = ptr.To(int64(milestone)) opt.Milestone = ptr.To(int64(milestone))
} }
state, ok := req.Params.Arguments["state"].(string) state, ok := req.GetArguments()["state"].(string)
if ok { if ok {
opt.State = ptr.To(gitea_sdk.StateType(state)) opt.State = ptr.To(gitea_sdk.StateType(state))
} }
@@ -258,3 +287,55 @@ func EditIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRes
return to.TextResult(issue) return to.TextResult(issue)
} }
func EditIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called EditIssueCommentFn")
owner, ok := req.GetArguments()["owner"].(string)
if !ok {
return to.ErrorResult(fmt.Errorf("owner is required"))
}
repo, ok := req.GetArguments()["repo"].(string)
if !ok {
return to.ErrorResult(fmt.Errorf("repo is required"))
}
commentID, ok := req.GetArguments()["commentID"].(float64)
if !ok {
return to.ErrorResult(fmt.Errorf("comment ID is required"))
}
body, ok := req.GetArguments()["body"].(string)
if !ok {
return to.ErrorResult(fmt.Errorf("body is required"))
}
opt := gitea_sdk.EditIssueCommentOption{
Body: body,
}
issueComment, _, err := gitea.Client().EditIssueComment(owner, repo, int64(commentID), opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("edit %v/%v/issues/comments/%v err: %v", owner, repo, int64(commentID), err))
}
return to.TextResult(issueComment)
}
func GetIssueCommentsByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetIssueCommentsByIndexFn")
owner, ok := req.GetArguments()["owner"].(string)
if !ok {
return to.ErrorResult(fmt.Errorf("owner is required"))
}
repo, ok := req.GetArguments()["repo"].(string)
if !ok {
return to.ErrorResult(fmt.Errorf("repo is required"))
}
index, ok := req.GetArguments()["index"].(float64)
if !ok {
return to.ErrorResult(fmt.Errorf("index is required"))
}
opt := gitea_sdk.ListIssueCommentOptions{}
issue, _, err := gitea.Client().ListIssueComments(owner, repo, int64(index), opt)
if err != nil {
return to.ErrorResult(fmt.Errorf("get %v/%v/issues/%v/comments err: %v", owner, repo, int64(index), err))
}
return to.TextResult(issue)
}

View File

@@ -2,6 +2,7 @@ package operation
import ( import (
"fmt" "fmt"
"time"
"gitea.com/gitea/gitea-mcp/operation/issue" "gitea.com/gitea/gitea-mcp/operation/issue"
"gitea.com/gitea/gitea-mcp/operation/pull" "gitea.com/gitea/gitea-mcp/operation/pull"
@@ -15,9 +16,7 @@ import (
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"
) )
var ( var mcpServer *server.MCPServer
mcpServer *server.MCPServer
)
func RegisterTool(s *server.MCPServer) { func RegisterTool(s *server.MCPServer) {
// User Tool // User Tool
@@ -46,17 +45,31 @@ func Run() error {
RegisterTool(mcpServer) RegisterTool(mcpServer)
switch flag.Mode { switch flag.Mode {
case "stdio": case "stdio":
if err := server.ServeStdio(mcpServer); err != nil { if err := server.ServeStdio(
mcpServer,
); err != nil {
return err return err
} }
case "sse": case "sse":
sseServer := server.NewSSEServer(mcpServer) sseServer := server.NewSSEServer(
mcpServer,
)
log.Infof("Gitea MCP SSE server listening on :%d", flag.Port) log.Infof("Gitea MCP SSE server listening on :%d", flag.Port)
if err := sseServer.Start(fmt.Sprintf(":%d", flag.Port)); err != nil { if err := sseServer.Start(fmt.Sprintf(":%d", flag.Port)); err != nil {
return err return err
} }
case "http":
httpServer := server.NewStreamableHTTPServer(
mcpServer,
server.WithLogger(log.New()),
server.WithHeartbeatInterval(30*time.Second),
)
log.Infof("Gitea MCP HTTP server listening on :%d", flag.Port)
if err := httpServer.Start(fmt.Sprintf(":%d", flag.Port)); err != nil {
return err
}
default: default:
return fmt.Errorf("invalid transport type: %s. Must be 'stdio' or 'sse'", flag.Mode) return fmt.Errorf("invalid transport type: %s. Must be 'stdio', 'sse' or 'http'", flag.Mode)
} }
return nil return nil
} }
@@ -67,5 +80,6 @@ func newMCPServer(version string) *server.MCPServer {
version, version,
server.WithToolCapabilities(true), server.WithToolCapabilities(true),
server.WithLogging(), server.WithLogging(),
server.WithRecovery(),
) )
} }

View File

@@ -72,15 +72,15 @@ func init() {
func GetPullRequestByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetPullRequestByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetPullRequestByIndexFn") log.Debugf("Called GetPullRequestByIndexFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }
index, ok := req.Params.Arguments["index"].(float64) index, ok := req.GetArguments()["index"].(float64)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("index is required")) return to.ErrorResult(fmt.Errorf("index is required"))
} }
@@ -94,25 +94,25 @@ func GetPullRequestByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp
func ListRepoPullRequestsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListRepoPullRequestsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListRepoPullRequests") log.Debugf("Called ListRepoPullRequests")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }
state, _ := req.Params.Arguments["state"].(string) state, _ := req.GetArguments()["state"].(string)
sort, ok := req.Params.Arguments["sort"].(string) sort, ok := req.GetArguments()["sort"].(string)
if !ok { if !ok {
sort = "recentupdate" sort = "recentupdate"
} }
milestone, _ := req.Params.Arguments["milestone"].(float64) milestone, _ := req.GetArguments()["milestone"].(float64)
page, ok := req.Params.Arguments["page"].(float64) page, ok := req.GetArguments()["page"].(float64)
if !ok { if !ok {
page = 1 page = 1
} }
pageSize, ok := req.Params.Arguments["pageSize"].(float64) pageSize, ok := req.GetArguments()["pageSize"].(float64)
if !ok { if !ok {
pageSize = 100 pageSize = 100
} }
@@ -135,27 +135,27 @@ func ListRepoPullRequestsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.
func CreatePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CreatePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreatePullRequestFn") log.Debugf("Called CreatePullRequestFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }
title, ok := req.Params.Arguments["title"].(string) title, ok := req.GetArguments()["title"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("title is required")) return to.ErrorResult(fmt.Errorf("title is required"))
} }
body, ok := req.Params.Arguments["body"].(string) body, ok := req.GetArguments()["body"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("body is required")) return to.ErrorResult(fmt.Errorf("body is required"))
} }
head, ok := req.Params.Arguments["head"].(string) head, ok := req.GetArguments()["head"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("head is required")) return to.ErrorResult(fmt.Errorf("head is required"))
} }
base, ok := req.Params.Arguments["base"].(string) base, ok := req.GetArguments()["base"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("base is required")) return to.ErrorResult(fmt.Errorf("base is required"))
} }

View File

@@ -62,19 +62,19 @@ func init() {
func CreateBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CreateBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateBranchFn") log.Debugf("Called CreateBranchFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }
branch, ok := req.Params.Arguments["branch"].(string) branch, ok := req.GetArguments()["branch"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("branch is required")) return to.ErrorResult(fmt.Errorf("branch is required"))
} }
oldBranch, _ := req.Params.Arguments["old_branch"].(string) oldBranch, _ := req.GetArguments()["old_branch"].(string)
_, _, err := gitea.Client().CreateBranch(owner, repo, gitea_sdk.CreateBranchOption{ _, _, err := gitea.Client().CreateBranch(owner, repo, gitea_sdk.CreateBranchOption{
BranchName: branch, BranchName: branch,
@@ -89,15 +89,15 @@ func CreateBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
func DeleteBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func DeleteBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteBranchFn") log.Debugf("Called DeleteBranchFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }
branch, ok := req.Params.Arguments["branch"].(string) branch, ok := req.GetArguments()["branch"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("branch is required")) return to.ErrorResult(fmt.Errorf("branch is required"))
} }
@@ -111,11 +111,11 @@ func DeleteBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
func ListBranchesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListBranchesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListBranchesFn") log.Debugf("Called ListBranchesFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }

View File

@@ -17,17 +17,15 @@ const (
ListRepoCommitsToolName = "list_repo_commits" ListRepoCommitsToolName = "list_repo_commits"
) )
var ( var ListRepoCommitsTool = mcp.NewTool(
ListRepoCommitsTool = mcp.NewTool( ListRepoCommitsToolName,
ListRepoCommitsToolName, mcp.WithDescription("List repository commits"),
mcp.WithDescription("List repository commits"), mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")), mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")), mcp.WithString("sha", mcp.Description("SHA or branch to start listing commits from")),
mcp.WithString("sha", mcp.Description("SHA or branch to start listing commits from")), mcp.WithString("path", mcp.Description("path indicates that only commits that include the path's file/dir should be returned.")),
mcp.WithString("path", mcp.Description("path indicates that only commits that include the path's file/dir should be returned.")), mcp.WithNumber("page", mcp.Required(), mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)),
mcp.WithNumber("page", mcp.Required(), mcp.Description("page number"), mcp.DefaultNumber(1), mcp.Min(1)), mcp.WithNumber("page_size", mcp.Required(), mcp.Description("page size"), mcp.DefaultNumber(50), mcp.Min(1)),
mcp.WithNumber("page_size", mcp.Required(), mcp.Description("page size"), mcp.DefaultNumber(50), mcp.Min(1)),
)
) )
func init() { func init() {
@@ -39,24 +37,24 @@ func init() {
func ListRepoCommitsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListRepoCommitsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListRepoCommitsFn") log.Debugf("Called ListRepoCommitsFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }
page, ok := req.Params.Arguments["page"].(float64) page, ok := req.GetArguments()["page"].(float64)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("page is required")) return to.ErrorResult(fmt.Errorf("page is required"))
} }
pageSize, ok := req.Params.Arguments["page_size"].(float64) pageSize, ok := req.GetArguments()["page_size"].(float64)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("page_size is required")) return to.ErrorResult(fmt.Errorf("page_size is required"))
} }
sha, _ := req.Params.Arguments["sha"].(string) sha, _ := req.GetArguments()["sha"].(string)
path, _ := req.Params.Arguments["path"].(string) path, _ := req.GetArguments()["path"].(string)
opt := gitea_sdk.ListCommitOptions{ opt := gitea_sdk.ListCommitOptions{
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{
Page: int(page), Page: int(page),

View File

@@ -16,6 +16,7 @@ import (
const ( const (
GetFileToolName = "get_file_content" GetFileToolName = "get_file_content"
GetDirToolName = "get_dir_content"
CreateFileToolName = "create_file" CreateFileToolName = "create_file"
UpdateFileToolName = "update_file" UpdateFileToolName = "update_file"
DeleteFileToolName = "delete_file" DeleteFileToolName = "delete_file"
@@ -31,6 +32,15 @@ var (
mcp.WithString("filePath", mcp.Required(), mcp.Description("file path")), mcp.WithString("filePath", mcp.Required(), mcp.Description("file path")),
) )
GetDirContentTool = mcp.NewTool(
GetDirToolName,
mcp.WithDescription("Get a list of entries in a directory"),
mcp.WithString("owner", mcp.Required(), mcp.Description("repository owner")),
mcp.WithString("repo", mcp.Required(), mcp.Description("repository name")),
mcp.WithString("ref", mcp.Required(), mcp.Description("ref can be branch/tag/commit")),
mcp.WithString("filePath", mcp.Required(), mcp.Description("directory path")),
)
CreateFileTool = mcp.NewTool( CreateFileTool = mcp.NewTool(
CreateFileToolName, CreateFileToolName,
mcp.WithDescription("Create file"), mcp.WithDescription("Create file"),
@@ -72,6 +82,10 @@ func init() {
Tool: GetFileContentTool, Tool: GetFileContentTool,
Handler: GetFileContentFn, Handler: GetFileContentFn,
}) })
Tool.RegisterRead(server.ServerTool{
Tool: GetDirContentTool,
Handler: GetDirContentFn,
})
Tool.RegisterWrite(server.ServerTool{ Tool.RegisterWrite(server.ServerTool{
Tool: CreateFileTool, Tool: CreateFileTool,
Handler: CreateFileFn, Handler: CreateFileFn,
@@ -88,16 +102,16 @@ func init() {
func GetFileContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetFileContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetFileFn") log.Debugf("Called GetFileFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }
ref, _ := req.Params.Arguments["ref"].(string) ref, _ := req.GetArguments()["ref"].(string)
filePath, ok := req.Params.Arguments["filePath"].(string) filePath, ok := req.GetArguments()["filePath"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("filePath is required")) return to.ErrorResult(fmt.Errorf("filePath is required"))
} }
@@ -108,23 +122,45 @@ func GetFileContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
return to.TextResult(content) return to.TextResult(content)
} }
func CreateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetDirContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateFileFn") log.Debugf("Called GetDirContentFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }
filePath, ok := req.Params.Arguments["filePath"].(string) ref, _ := req.GetArguments()["ref"].(string)
filePath, ok := req.GetArguments()["filePath"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("filePath is required")) return to.ErrorResult(fmt.Errorf("filePath is required"))
} }
content, _ := req.Params.Arguments["content"].(string) content, _, err := gitea.Client().ListContents(owner, repo, ref, filePath)
message, _ := req.Params.Arguments["message"].(string) if err != nil {
branchName, _ := req.Params.Arguments["branch_name"].(string) return to.ErrorResult(fmt.Errorf("get dir content err: %v", err))
}
return to.TextResult(content)
}
func CreateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateFileFn")
owner, ok := req.GetArguments()["owner"].(string)
if !ok {
return to.ErrorResult(fmt.Errorf("owner is required"))
}
repo, ok := req.GetArguments()["repo"].(string)
if !ok {
return to.ErrorResult(fmt.Errorf("repo is required"))
}
filePath, ok := req.GetArguments()["filePath"].(string)
if !ok {
return to.ErrorResult(fmt.Errorf("filePath is required"))
}
content, _ := req.GetArguments()["content"].(string)
message, _ := req.GetArguments()["message"].(string)
branchName, _ := req.GetArguments()["branch_name"].(string)
opt := gitea_sdk.CreateFileOptions{ opt := gitea_sdk.CreateFileOptions{
Content: base64.StdEncoding.EncodeToString([]byte(content)), Content: base64.StdEncoding.EncodeToString([]byte(content)),
FileOptions: gitea_sdk.FileOptions{ FileOptions: gitea_sdk.FileOptions{
@@ -142,25 +178,25 @@ func CreateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
func UpdateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func UpdateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called UpdateFileFn") log.Debugf("Called UpdateFileFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }
filePath, ok := req.Params.Arguments["filePath"].(string) filePath, ok := req.GetArguments()["filePath"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("filePath is required")) return to.ErrorResult(fmt.Errorf("filePath is required"))
} }
sha, ok := req.Params.Arguments["sha"].(string) sha, ok := req.GetArguments()["sha"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("sha is required")) return to.ErrorResult(fmt.Errorf("sha is required"))
} }
content, _ := req.Params.Arguments["content"].(string) content, _ := req.GetArguments()["content"].(string)
message, _ := req.Params.Arguments["message"].(string) message, _ := req.GetArguments()["message"].(string)
branchName, _ := req.Params.Arguments["branch_name"].(string) branchName, _ := req.GetArguments()["branch_name"].(string)
opt := gitea_sdk.UpdateFileOptions{ opt := gitea_sdk.UpdateFileOptions{
SHA: sha, SHA: sha,
@@ -179,21 +215,21 @@ func UpdateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
func DeleteFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func DeleteFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteFileFn") log.Debugf("Called DeleteFileFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("owner is required")) return to.ErrorResult(fmt.Errorf("owner is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("repo is required")) return to.ErrorResult(fmt.Errorf("repo is required"))
} }
filePath, ok := req.Params.Arguments["filePath"].(string) filePath, ok := req.GetArguments()["filePath"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("filePath is required")) return to.ErrorResult(fmt.Errorf("filePath is required"))
} }
message, _ := req.Params.Arguments["message"].(string) message, _ := req.GetArguments()["message"].(string)
branchName, _ := req.Params.Arguments["branch_name"].(string) branchName, _ := req.GetArguments()["branch_name"].(string)
sha, ok := req.Params.Arguments["sha"].(string) sha, ok := req.GetArguments()["sha"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("sha is required")) return to.ErrorResult(fmt.Errorf("sha is required"))
} }

View File

@@ -109,28 +109,28 @@ type ListReleaseResult struct {
func CreateReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CreateReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateReleasesFn") log.Debugf("Called CreateReleasesFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return nil, fmt.Errorf("owner is required") return nil, fmt.Errorf("owner is required")
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return nil, fmt.Errorf("repo is required") return nil, fmt.Errorf("repo is required")
} }
tagName, ok := req.Params.Arguments["tag_name"].(string) tagName, ok := req.GetArguments()["tag_name"].(string)
if !ok { if !ok {
return nil, fmt.Errorf("tag_name is required") return nil, fmt.Errorf("tag_name is required")
} }
target, ok := req.Params.Arguments["target"].(string) target, ok := req.GetArguments()["target"].(string)
if !ok { if !ok {
return nil, fmt.Errorf("target is required") return nil, fmt.Errorf("target is required")
} }
title, ok := req.Params.Arguments["title"].(string) title, ok := req.GetArguments()["title"].(string)
if !ok { if !ok {
return nil, fmt.Errorf("title is required") return nil, fmt.Errorf("title is required")
} }
isDraft, _ := req.Params.Arguments["is_draft"].(bool) isDraft, _ := req.GetArguments()["is_draft"].(bool)
isPreRelease, _ := req.Params.Arguments["is_pre_release"].(bool) isPreRelease, _ := req.GetArguments()["is_pre_release"].(bool)
_, _, err := gitea.Client().CreateRelease(owner, repo, gitea_sdk.CreateReleaseOption{ _, _, err := gitea.Client().CreateRelease(owner, repo, gitea_sdk.CreateReleaseOption{
TagName: tagName, TagName: tagName,
@@ -148,15 +148,15 @@ func CreateReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
func DeleteReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func DeleteReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteReleaseFn") log.Debugf("Called DeleteReleaseFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return nil, fmt.Errorf("owner is required") return nil, fmt.Errorf("owner is required")
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return nil, fmt.Errorf("repo is required") return nil, fmt.Errorf("repo is required")
} }
id, ok := req.Params.Arguments["id"].(float64) id, ok := req.GetArguments()["id"].(float64)
if !ok { if !ok {
return nil, fmt.Errorf("id is required") return nil, fmt.Errorf("id is required")
} }
@@ -171,15 +171,15 @@ func DeleteReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
func GetReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetReleaseFn") log.Debugf("Called GetReleaseFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return nil, fmt.Errorf("owner is required") return nil, fmt.Errorf("owner is required")
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return nil, fmt.Errorf("repo is required") return nil, fmt.Errorf("repo is required")
} }
id, ok := req.Params.Arguments["id"].(float64) id, ok := req.GetArguments()["id"].(float64)
if !ok { if !ok {
return nil, fmt.Errorf("id is required") return nil, fmt.Errorf("id is required")
} }
@@ -194,11 +194,11 @@ func GetReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
func GetLatestReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetLatestReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetLatestReleaseFn") log.Debugf("Called GetLatestReleaseFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return nil, fmt.Errorf("owner is required") return nil, fmt.Errorf("owner is required")
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return nil, fmt.Errorf("repo is required") return nil, fmt.Errorf("repo is required")
} }
@@ -213,26 +213,34 @@ func GetLatestReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
func ListReleasesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListReleasesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListReleasesFn") log.Debugf("Called ListReleasesFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return nil, fmt.Errorf("owner is required") return nil, fmt.Errorf("owner is required")
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return nil, fmt.Errorf("repo is required") return nil, fmt.Errorf("repo is required")
} }
isDraft, _ := req.Params.Arguments["is_draft"].(bool) var pIsDraft *bool
isPreRelease, _ := req.Params.Arguments["is_pre_release"].(bool) isDraft, ok := req.GetArguments()["is_draft"].(bool)
page, _ := req.Params.Arguments["page"].(float64) if ok {
pageSize, _ := req.Params.Arguments["pageSize"].(float64) pIsDraft = ptr.To(isDraft)
}
var pIsPreRelease *bool
isPreRelease, ok := req.GetArguments()["is_pre_release"].(bool)
if ok {
pIsPreRelease = ptr.To(isPreRelease)
}
page, _ := req.GetArguments()["page"].(float64)
pageSize, _ := req.GetArguments()["pageSize"].(float64)
releases, _, err := gitea.Client().ListReleases(owner, repo, gitea_sdk.ListReleasesOptions{ releases, _, err := gitea.Client().ListReleases(owner, repo, gitea_sdk.ListReleasesOptions{
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{
Page: int(page), Page: int(page),
PageSize: int(pageSize), PageSize: int(pageSize),
}, },
IsDraft: ptr.To(isDraft), IsDraft: pIsDraft,
IsPreRelease: ptr.To(isPreRelease), IsPreRelease: pIsPreRelease,
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("list releases error: %v", err) return nil, fmt.Errorf("list releases error: %v", err)

View File

@@ -107,19 +107,19 @@ func RegisterTool(s *server.MCPServer) {
func CreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateRepoFn") log.Debugf("Called CreateRepoFn")
name, ok := req.Params.Arguments["name"].(string) name, ok := req.GetArguments()["name"].(string)
if !ok { if !ok {
return to.ErrorResult(errors.New("repository name is required")) return to.ErrorResult(errors.New("repository name is required"))
} }
description, _ := req.Params.Arguments["description"].(string) description, _ := req.GetArguments()["description"].(string)
private, _ := req.Params.Arguments["private"].(bool) private, _ := req.GetArguments()["private"].(bool)
issueLabels, _ := req.Params.Arguments["issue_labels"].(string) issueLabels, _ := req.GetArguments()["issue_labels"].(string)
autoInit, _ := req.Params.Arguments["auto_init"].(bool) autoInit, _ := req.GetArguments()["auto_init"].(bool)
template, _ := req.Params.Arguments["template"].(bool) template, _ := req.GetArguments()["template"].(bool)
gitignores, _ := req.Params.Arguments["gitignores"].(string) gitignores, _ := req.GetArguments()["gitignores"].(string)
license, _ := req.Params.Arguments["license"].(string) license, _ := req.GetArguments()["license"].(string)
readme, _ := req.Params.Arguments["readme"].(string) readme, _ := req.GetArguments()["readme"].(string)
defaultBranch, _ := req.Params.Arguments["default_branch"].(string) defaultBranch, _ := req.GetArguments()["default_branch"].(string)
opt := gitea_sdk.CreateRepoOption{ opt := gitea_sdk.CreateRepoOption{
Name: name, Name: name,
@@ -142,20 +142,20 @@ func CreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
func ForkRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ForkRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ForkRepoFn") log.Debugf("Called ForkRepoFn")
user, ok := req.Params.Arguments["user"].(string) user, ok := req.GetArguments()["user"].(string)
if !ok { if !ok {
return to.ErrorResult(errors.New("user name is required")) return to.ErrorResult(errors.New("user name is required"))
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return to.ErrorResult(errors.New("repository name is required")) return to.ErrorResult(errors.New("repository name is required"))
} }
organization, ok := req.Params.Arguments["organization"].(string) organization, ok := req.GetArguments()["organization"].(string)
organizationPtr := ptr.To(organization) organizationPtr := ptr.To(organization)
if !ok || organization == "" { if !ok || organization == "" {
organizationPtr = nil organizationPtr = nil
} }
name, ok := req.Params.Arguments["name"].(string) name, ok := req.GetArguments()["name"].(string)
namePtr := ptr.To(name) namePtr := ptr.To(name)
if !ok || name == "" { if !ok || name == "" {
namePtr = nil namePtr = nil
@@ -173,11 +173,11 @@ func ForkRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResu
func ListMyReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListMyReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListMyReposFn") log.Debugf("Called ListMyReposFn")
page, ok := req.Params.Arguments["page"].(float64) page, ok := req.GetArguments()["page"].(float64)
if !ok { if !ok {
page = 1 page = 1
} }
pageSize, ok := req.Params.Arguments["pageSize"].(float64) pageSize, ok := req.GetArguments()["pageSize"].(float64)
if !ok { if !ok {
pageSize = 100 pageSize = 100
} }

View File

@@ -87,20 +87,20 @@ type ListTagResult struct {
func CreateTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CreateTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateTagFn") log.Debugf("Called CreateTagFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return nil, fmt.Errorf("owner is required") return nil, fmt.Errorf("owner is required")
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return nil, fmt.Errorf("repo is required") return nil, fmt.Errorf("repo is required")
} }
tagName, ok := req.Params.Arguments["tag_name"].(string) tagName, ok := req.GetArguments()["tag_name"].(string)
if !ok { if !ok {
return nil, fmt.Errorf("tag_name is required") return nil, fmt.Errorf("tag_name is required")
} }
target, _ := req.Params.Arguments["target"].(string) target, _ := req.GetArguments()["target"].(string)
message, _ := req.Params.Arguments["message"].(string) message, _ := req.GetArguments()["message"].(string)
_, _, err := gitea.Client().CreateTag(owner, repo, gitea_sdk.CreateTagOption{ _, _, err := gitea.Client().CreateTag(owner, repo, gitea_sdk.CreateTagOption{
TagName: tagName, TagName: tagName,
@@ -116,15 +116,15 @@ func CreateTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRes
func DeleteTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func DeleteTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called DeleteTagFn") log.Debugf("Called DeleteTagFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return nil, fmt.Errorf("owner is required") return nil, fmt.Errorf("owner is required")
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return nil, fmt.Errorf("repo is required") return nil, fmt.Errorf("repo is required")
} }
tagName, ok := req.Params.Arguments["tag_name"].(string) tagName, ok := req.GetArguments()["tag_name"].(string)
if !ok { if !ok {
return nil, fmt.Errorf("tag_name is required") return nil, fmt.Errorf("tag_name is required")
} }
@@ -139,15 +139,15 @@ func DeleteTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRes
func GetTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetTagFn") log.Debugf("Called GetTagFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return nil, fmt.Errorf("owner is required") return nil, fmt.Errorf("owner is required")
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return nil, fmt.Errorf("repo is required") return nil, fmt.Errorf("repo is required")
} }
tagName, ok := req.Params.Arguments["tag_name"].(string) tagName, ok := req.GetArguments()["tag_name"].(string)
if !ok { if !ok {
return nil, fmt.Errorf("tag_name is required") return nil, fmt.Errorf("tag_name is required")
} }
@@ -162,16 +162,16 @@ func GetTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult
func ListTagsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListTagsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListTagsFn") log.Debugf("Called ListTagsFn")
owner, ok := req.Params.Arguments["owner"].(string) owner, ok := req.GetArguments()["owner"].(string)
if !ok { if !ok {
return nil, fmt.Errorf("owner is required") return nil, fmt.Errorf("owner is required")
} }
repo, ok := req.Params.Arguments["repo"].(string) repo, ok := req.GetArguments()["repo"].(string)
if !ok { if !ok {
return nil, fmt.Errorf("repo is required") return nil, fmt.Errorf("repo is required")
} }
page, _ := req.Params.Arguments["page"].(float64) page, _ := req.GetArguments()["page"].(float64)
pageSize, _ := req.Params.Arguments["pageSize"].(float64) pageSize, _ := req.GetArguments()["pageSize"].(float64)
tags, _, err := gitea.Client().ListRepoTags(owner, repo, gitea_sdk.ListRepoTagsOptions{ tags, _, err := gitea.Client().ListRepoTags(owner, repo, gitea_sdk.ListRepoTagsOptions{
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{

View File

@@ -75,15 +75,15 @@ func init() {
func SearchUsersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func SearchUsersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called SearchUsersFn") log.Debugf("Called SearchUsersFn")
keyword, ok := req.Params.Arguments["keyword"].(string) keyword, ok := req.GetArguments()["keyword"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("keyword is required")) return to.ErrorResult(fmt.Errorf("keyword is required"))
} }
page, ok := req.Params.Arguments["page"].(float64) page, ok := req.GetArguments()["page"].(float64)
if !ok { if !ok {
page = 1 page = 1
} }
pageSize, ok := req.Params.Arguments["pageSize"].(float64) pageSize, ok := req.GetArguments()["pageSize"].(float64)
if !ok { if !ok {
pageSize = 100 pageSize = 100
} }
@@ -103,20 +103,20 @@ func SearchUsersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
func SearchOrgTeamsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func SearchOrgTeamsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called SearchOrgTeamsFn") log.Debugf("Called SearchOrgTeamsFn")
org, ok := req.Params.Arguments["org"].(string) org, ok := req.GetArguments()["org"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("organization is required")) return to.ErrorResult(fmt.Errorf("organization is required"))
} }
query, ok := req.Params.Arguments["query"].(string) query, ok := req.GetArguments()["query"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("query is required")) return to.ErrorResult(fmt.Errorf("query is required"))
} }
includeDescription, _ := req.Params.Arguments["includeDescription"].(bool) includeDescription, _ := req.GetArguments()["includeDescription"].(bool)
page, ok := req.Params.Arguments["page"].(float64) page, ok := req.GetArguments()["page"].(float64)
if !ok { if !ok {
page = 1 page = 1
} }
pageSize, ok := req.Params.Arguments["pageSize"].(float64) pageSize, ok := req.GetArguments()["pageSize"].(float64)
if !ok { if !ok {
pageSize = 100 pageSize = 100
} }
@@ -137,22 +137,30 @@ func SearchOrgTeamsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
func SearchReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func SearchReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called SearchReposFn") log.Debugf("Called SearchReposFn")
keyword, ok := req.Params.Arguments["keyword"].(string) keyword, ok := req.GetArguments()["keyword"].(string)
if !ok { if !ok {
return to.ErrorResult(fmt.Errorf("keyword is required")) return to.ErrorResult(fmt.Errorf("keyword is required"))
} }
keywordIsTopic, _ := req.Params.Arguments["keywordIsTopic"].(bool) keywordIsTopic, _ := req.GetArguments()["keywordIsTopic"].(bool)
keywordInDescription, _ := req.Params.Arguments["keywordInDescription"].(bool) keywordInDescription, _ := req.GetArguments()["keywordInDescription"].(bool)
ownerID, _ := req.Params.Arguments["ownerID"].(float64) ownerID, _ := req.GetArguments()["ownerID"].(float64)
isPrivate, _ := req.Params.Arguments["isPrivate"].(bool) var pIsPrivate *bool
isArchived, _ := req.Params.Arguments["isArchived"].(bool) isPrivate, ok := req.GetArguments()["isPrivate"].(bool)
sort, _ := req.Params.Arguments["sort"].(string) if ok {
order, _ := req.Params.Arguments["order"].(string) pIsPrivate = ptr.To(isPrivate)
page, ok := req.Params.Arguments["page"].(float64) }
var pIsArchived *bool
isArchived, ok := req.GetArguments()["isArchived"].(bool)
if ok {
pIsArchived = ptr.To(isArchived)
}
sort, _ := req.GetArguments()["sort"].(string)
order, _ := req.GetArguments()["order"].(string)
page, ok := req.GetArguments()["page"].(float64)
if !ok { if !ok {
page = 1 page = 1
} }
pageSize, ok := req.Params.Arguments["pageSize"].(float64) pageSize, ok := req.GetArguments()["pageSize"].(float64)
if !ok { if !ok {
pageSize = 100 pageSize = 100
} }
@@ -161,8 +169,8 @@ func SearchReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
KeywordIsTopic: keywordIsTopic, KeywordIsTopic: keywordIsTopic,
KeywordInDescription: keywordInDescription, KeywordInDescription: keywordInDescription,
OwnerID: int64(ownerID), OwnerID: int64(ownerID),
IsPrivate: ptr.To(isPrivate), IsPrivate: pIsPrivate,
IsArchived: ptr.To(isArchived), IsArchived: pIsArchived,
Sort: sort, Sort: sort,
Order: order, Order: order,
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{

View File

@@ -59,11 +59,11 @@ func GetUserInfoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
func GetUserOrgsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetUserOrgsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetUserOrgsFn") log.Debugf("Called GetUserOrgsFn")
page, ok := req.Params.Arguments["page"].(float64) page, ok := req.GetArguments()["page"].(float64)
if !ok || page < 1 { if !ok || page < 1 {
page = 1 page = 1
} }
pageSize, ok := req.Params.Arguments["pageSize"].(float64) pageSize, ok := req.GetArguments()["pageSize"].(float64)
if !ok || pageSize < 1 { if !ok || pageSize < 1 {
pageSize = 100 pageSize = 100
} }

View File

@@ -19,11 +19,9 @@ const (
GetGiteaMCPServerVersion = "get_gitea_mcp_server_version" GetGiteaMCPServerVersion = "get_gitea_mcp_server_version"
) )
var ( var GetGiteaMCPServerVersionTool = mcp.NewTool(
GetGiteaMCPServerVersionTool = mcp.NewTool( GetGiteaMCPServerVersion,
GetGiteaMCPServerVersion, mcp.WithDescription("Get Gitea MCP Server Version"),
mcp.WithDescription("Get Gitea MCP Server Version"),
)
) )
func init() { func init() {

View File

@@ -22,19 +22,20 @@ func Client() *gitea.Client {
if client != nil { if client != nil {
return return
} }
httpClient := &http.Client{
Transport: http.DefaultTransport,
}
opts := []gitea.ClientOption{ opts := []gitea.ClientOption{
gitea.SetToken(flag.Token), gitea.SetToken(flag.Token),
} }
if flag.Insecure { if flag.Insecure {
httpClient := &http.Client{ httpClient.Transport.(*http.Transport).TLSClientConfig = &tls.Config{
Transport: &http.Transport{ InsecureSkipVerify: true,
TLSClientConfig: &tls.Config{
InsecureSkipVerify: true,
},
},
} }
opts = append(opts, gitea.SetHTTPClient(httpClient))
} }
opts = append(opts, gitea.SetHTTPClient(httpClient))
if flag.Debug { if flag.Debug {
opts = append(opts, gitea.SetDebugMode()) opts = append(opts, gitea.SetDebugMode())
} }

View File

@@ -19,47 +19,55 @@ var (
func Default() *zap.Logger { func Default() *zap.Logger {
defaultLoggerOnce.Do(func() { defaultLoggerOnce.Do(func() {
if defaultLogger == nil { if defaultLogger != nil {
ec := zap.NewProductionEncoderConfig() return
ec.EncodeTime = zapcore.TimeEncoderOfLayout(time.DateTime)
ec.EncodeLevel = zapcore.CapitalLevelEncoder
var ws zapcore.WriteSyncer
var wss []zapcore.WriteSyncer
home, _ := os.UserHomeDir()
if home == "" {
home = os.TempDir()
}
wss = append(wss, zapcore.AddSync(&lumberjack.Logger{
Filename: fmt.Sprintf("%s/.gitea-mcp/gitea-mcp.log", home),
MaxSize: 100,
MaxBackups: 10,
MaxAge: 30,
}))
if flag.Mode == "sse" {
wss = append(wss, zapcore.AddSync(os.Stdout))
}
ws = zapcore.NewMultiWriteSyncer(wss...)
enc := zapcore.NewConsoleEncoder(ec)
var level zapcore.Level
if flag.Debug {
level = zapcore.DebugLevel
} else {
level = zapcore.InfoLevel
}
core := zapcore.NewCore(enc, ws, level)
options := []zap.Option{
zap.AddStacktrace(zapcore.DPanicLevel),
zap.AddCaller(),
zap.AddCallerSkip(1),
}
defaultLogger = zap.New(core, options...)
} }
ec := zap.NewProductionEncoderConfig()
ec.EncodeTime = zapcore.TimeEncoderOfLayout(time.DateTime)
ec.EncodeLevel = zapcore.CapitalLevelEncoder
var ws zapcore.WriteSyncer
var wss []zapcore.WriteSyncer
home, _ := os.UserHomeDir()
if home == "" {
home = os.TempDir()
}
logDir := fmt.Sprintf("%s/.gitea-mcp", home)
if err := os.MkdirAll(logDir, 0o700); err != nil {
// Fallback to temp directory if creation fails
logDir = os.TempDir()
}
wss = append(wss, zapcore.AddSync(&lumberjack.Logger{
Filename: fmt.Sprintf("%s/gitea-mcp.log", logDir),
MaxSize: 100,
MaxBackups: 10,
MaxAge: 30,
}))
if flag.Mode == "http" || flag.Mode == "sse" {
wss = append(wss, zapcore.AddSync(os.Stdout))
}
ws = zapcore.NewMultiWriteSyncer(wss...)
enc := zapcore.NewConsoleEncoder(ec)
var level zapcore.Level
if flag.Debug {
level = zapcore.DebugLevel
} else {
level = zapcore.InfoLevel
}
core := zapcore.NewCore(enc, ws, level)
options := []zap.Option{
zap.AddStacktrace(zapcore.DPanicLevel),
zap.AddCaller(),
zap.AddCallerSkip(1),
}
defaultLogger = zap.New(core, options...)
}) })
return defaultLogger return defaultLogger
@@ -71,8 +79,22 @@ func SetDefault(logger *zap.Logger) {
} }
} }
func Logger() *zap.Logger { func New() *Logger {
return defaultLogger return &Logger{
defaultLogger: Default(),
}
}
type Logger struct {
defaultLogger *zap.Logger
}
func (l *Logger) Infof(msg string, args ...any) {
l.defaultLogger.Sugar().Infof(msg, args...)
}
func (l *Logger) Errorf(msg string, args ...any) {
l.defaultLogger.Sugar().Errorf(msg, args...)
} }
func Debug(msg string, fields ...zap.Field) { func Debug(msg string, fields ...zap.Field) {

View File

@@ -12,8 +12,8 @@ type Tool struct {
func New() *Tool { func New() *Tool {
return &Tool{ return &Tool{
write: make([]server.ServerTool, 100), write: make([]server.ServerTool, 0, 100),
read: make([]server.ServerTool, 100), read: make([]server.ServerTool, 0, 100),
} }
} }