mirror of
https://gitea.com/gitea/gitea-mcp.git
synced 2026-08-03 15:49:23 +02:00
Compare commits
4 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 3fcd864582 | |||
| 4eaeb252a0 | |||
| 290d06b40b | |||
| 80114e32e6 |
@@ -14,18 +14,18 @@ jobs:
|
|||||||
DOCKER_LATEST: nightly
|
DOCKER_LATEST: nightly
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # all history for all branches and tags
|
fetch-depth: 0 # all history for all branches and tags
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4
|
||||||
|
|
||||||
- name: Set up Docker BuildX
|
- name: Set up Docker BuildX
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@371161bbe7024a29a25c5e19bfcbc0804fe9ad2c # v4
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USER }}
|
username: ${{ secrets.DOCKER_USER }}
|
||||||
password: ${{ secrets.DOCKER_TOKEN }}
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
@@ -37,7 +37,7 @@ jobs:
|
|||||||
echo REPO_VERSION=$(git describe --tags --always | sed 's/-/+/' | sed 's/^v//') >> $GITHUB_OUTPUT
|
echo REPO_VERSION=$(git describe --tags --always | sed 's/-/+/' | sed 's/^v//') >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
|
|||||||
@@ -10,11 +10,11 @@ jobs:
|
|||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0
|
fetch-depth: 0
|
||||||
- name: Set up Go
|
- name: Set up Go
|
||||||
uses: actions/setup-go@v6
|
uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
|
||||||
with:
|
with:
|
||||||
go-version: stable
|
go-version: stable
|
||||||
- name: Install GoReleaser
|
- name: Install GoReleaser
|
||||||
@@ -32,18 +32,18 @@ jobs:
|
|||||||
DOCKER_LATEST: latest
|
DOCKER_LATEST: latest
|
||||||
steps:
|
steps:
|
||||||
- name: Checkout
|
- name: Checkout
|
||||||
uses: actions/checkout@v6
|
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
with:
|
with:
|
||||||
fetch-depth: 0 # all history for all branches and tags
|
fetch-depth: 0 # all history for all branches and tags
|
||||||
|
|
||||||
- name: Set up QEMU
|
- name: Set up QEMU
|
||||||
uses: docker/setup-qemu-action@v3
|
uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4
|
||||||
|
|
||||||
- name: Set up Docker BuildX
|
- name: Set up Docker BuildX
|
||||||
uses: docker/setup-buildx-action@v3
|
uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4
|
||||||
|
|
||||||
- name: Login to DockerHub
|
- name: Login to DockerHub
|
||||||
uses: docker/login-action@v3
|
uses: docker/login-action@371161bbe7024a29a25c5e19bfcbc0804fe9ad2c # v4
|
||||||
with:
|
with:
|
||||||
username: ${{ secrets.DOCKER_USER }}
|
username: ${{ secrets.DOCKER_USER }}
|
||||||
password: ${{ secrets.DOCKER_TOKEN }}
|
password: ${{ secrets.DOCKER_TOKEN }}
|
||||||
@@ -55,7 +55,7 @@ jobs:
|
|||||||
echo REPO_VERSION=${GITHUB_REF_NAME#v} >> $GITHUB_OUTPUT
|
echo REPO_VERSION=${GITHUB_REF_NAME#v} >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Build and push
|
- name: Build and push
|
||||||
uses: docker/build-push-action@v6
|
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
|
|||||||
@@ -7,8 +7,8 @@ jobs:
|
|||||||
check-and-test:
|
check-and-test:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v6
|
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
|
||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@b7ad1dad31e06c5925ef5d2fc7ad053ef454303e # v7
|
||||||
with:
|
with:
|
||||||
go-version-file: 'go.mod'
|
go-version-file: 'go.mod'
|
||||||
- name: lint
|
- name: lint
|
||||||
|
|||||||
@@ -1,12 +1,16 @@
|
|||||||
|
- Never assume, verify before claiming
|
||||||
- Use `make help` to find available development targets
|
- Use `make help` to find available development targets
|
||||||
- Run `make fmt` to format `.go` files, and run `make lint-go` to lint them
|
- PR descriptions: minimal, only what and why, no task lists or file listings
|
||||||
- Run `make tidy` after any `go.mod` changes
|
- Reference issues and PRs by full URL, not by number
|
||||||
- Run single go tests with `go test -run '^TestName$' ./modulepath/`
|
|
||||||
- Ensure no trailing whitespace in edited files
|
|
||||||
- Use Conventional Commits for commit messages and PR titles, e.g. `type(scope): subject`; `!` before the colon if breaking. Use `test` type for test-only changes.
|
- Use Conventional Commits for commit messages and PR titles, e.g. `type(scope): subject`; `!` before the colon if breaking. Use `test` type for test-only changes.
|
||||||
|
- Add an `Assisted-by: AGENT_NAME:MODEL_VERSION` trailer to commit messages, never `Co-Authored-By` or `Signed-off-by`
|
||||||
|
- Attribute agent authorship on one trailing line in issue and pull request comments, never as a PR description section
|
||||||
- Never force-push, amend, or squash unless asked. Use new commits and normal push for pull request updates
|
- Never force-push, amend, or squash unless asked. Use new commits and normal push for pull request updates
|
||||||
- Preserve existing code comments, do not remove or rewrite comments that are still relevant
|
- Keep comments short, prefer same-line, explain why, never narrate code. Preserve existing ones that still apply
|
||||||
- Keep comments short, prefer same-line, explain why, never narrate code
|
- Ensure no trailing whitespace in edited files
|
||||||
|
- Run `make fmt` to format `.go` files, `make lint-go` to lint them, and `make tidy` after any `go.mod` changes
|
||||||
|
- Fix the cause rather than disabling a linter or weakening a test. Where unavoidable, use the narrowest scope with a trailing comment giving the reason
|
||||||
- Register new tools with `Tool.RegisterRead` or `Tool.RegisterWrite`, and add them to the tool tables in `README.md`, `README.zh-cn.md` and `README.zh-tw.md`
|
- Register new tools with `Tool.RegisterRead` or `Tool.RegisterWrite`, and add them to the tool tables in `README.md`, `README.zh-cn.md` and `README.zh-tw.md`
|
||||||
- Include authorship attribution in issue and pull request comments
|
- Run single go tests with `go test -run '^TestName$' ./modulepath/`
|
||||||
- Add `Co-Authored-By` lines to all commits, indicating name and model used
|
- Write the fewest, fastest tests covering the behavior, extending an existing one where possible. Prefer unit tests where logic is testable in isolation
|
||||||
|
- Wait on a deterministic condition rather than `sleep`
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
# syntax=docker/dockerfile:1.4
|
# syntax=docker/dockerfile:1.25
|
||||||
|
|
||||||
# Build stage
|
# Build stage
|
||||||
FROM --platform=$BUILDPLATFORM golang:1.26-alpine AS builder
|
FROM --platform=$BUILDPLATFORM golang:1.26-alpine AS builder
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ EXECUTABLE := gitea-mcp
|
|||||||
VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')
|
VERSION ?= $(shell git describe --tags --always | sed 's/-/+/' | sed 's/^v//')
|
||||||
LDFLAGS := -X "main.Version=$(VERSION)"
|
LDFLAGS := -X "main.Version=$(VERSION)"
|
||||||
|
|
||||||
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2
|
GOLANGCI_LINT_PACKAGE ?= github.com/golangci/golangci-lint/v2/cmd/golangci-lint@v2.12.2 # renovate: datasource=go
|
||||||
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1.3.0
|
GOVULNCHECK_PACKAGE ?= golang.org/x/vuln/cmd/govulncheck@v1.6.0 # renovate: datasource=go
|
||||||
|
|
||||||
.PHONY: help
|
.PHONY: help
|
||||||
help: ## print this help message
|
help: ## print this help message
|
||||||
|
|||||||
@@ -155,9 +155,9 @@ Once configured, try `list all my repositories` in the chat box.
|
|||||||
| issue_read | issue | Read | Read issue: details, comments, or labels |
|
| issue_read | issue | Read | Read issue: details, comments, or labels |
|
||||||
| issue_write | issue | Write | Write issues: create, update, manage comments and labels |
|
| issue_write | issue | Write | Write issues: create, update, manage comments and labels |
|
||||||
| list_pull_requests | pull_request | Read | List repository pull requests |
|
| list_pull_requests | pull_request | Read | List repository pull requests |
|
||||||
| pull_request_read | pull_request | Read | Read pull request: details, diff, changed files, head commit status, reviews |
|
| pull_request_read | pull_request | Read | Read pull request: details, diff, files, status, reviews, review comments |
|
||||||
| pull_request_write | pull_request | Write | Write pull requests: create, update, close, reopen, merge, update branch, manage reviewers |
|
| pull_request_write | pull_request | Write | Write pull requests: create, update, close, reopen, merge, update branch, manage reviewers |
|
||||||
| pull_request_review_write | pull_request | Write | Write PR reviews: create, submit, delete, dismiss |
|
| pull_request_review_write | pull_request | Write | Write PR reviews: create, submit, delete, dismiss, reply to and resolve review comments |
|
||||||
| actions_config_read | actions | Read | Read Actions secrets and variables |
|
| actions_config_read | actions | Read | Read Actions secrets and variables |
|
||||||
| actions_config_write | actions | Write | Write Actions secrets and variables: upsert, create, update, delete |
|
| actions_config_write | actions | Write | Write Actions secrets and variables: upsert, create, update, delete |
|
||||||
| actions_run_read | actions | Read | Read Actions workflows, runs, jobs, logs, and artifacts |
|
| actions_run_read | actions | Read | Read Actions workflows, runs, jobs, logs, and artifacts |
|
||||||
|
|||||||
+2
-2
@@ -155,9 +155,9 @@ Cursor 等客户端可使用 stdio 命令:
|
|||||||
| issue_read | issue | 读取 | 读取问题:详情、评论或标签 |
|
| issue_read | issue | 读取 | 读取问题:详情、评论或标签 |
|
||||||
| issue_write | issue | 写入 | 写入问题:创建、更新、管理评论和标签 |
|
| issue_write | issue | 写入 | 写入问题:创建、更新、管理评论和标签 |
|
||||||
| list_pull_requests | pull_request | 读取 | 列出仓库拉取请求 |
|
| list_pull_requests | pull_request | 读取 | 列出仓库拉取请求 |
|
||||||
| pull_request_read | pull_request | 读取 | 读取拉取请求:详情、差异、变更文件、头部提交状态、审查 |
|
| pull_request_read | pull_request | 读取 | 读取拉取请求:详情、差异、变更文件、头部提交状态、审查、审查评论 |
|
||||||
| pull_request_write | pull_request | 写入 | 写入拉取请求:创建、更新、关闭、重新打开、合并、更新分支、管理审查者 |
|
| pull_request_write | pull_request | 写入 | 写入拉取请求:创建、更新、关闭、重新打开、合并、更新分支、管理审查者 |
|
||||||
| pull_request_review_write | pull_request | 写入 | 写入 PR 审查:创建、提交、删除、驳回 |
|
| pull_request_review_write | pull_request | 写入 | 写入 PR 审查:创建、提交、删除、驳回、回复和解决审查评论 |
|
||||||
| actions_config_read | actions | 读取 | 读取 Actions 密钥和变量 |
|
| actions_config_read | actions | 读取 | 读取 Actions 密钥和变量 |
|
||||||
| actions_config_write | actions | 写入 | 写入 Actions 密钥和变量:更新插入、创建、更新、删除 |
|
| actions_config_write | actions | 写入 | 写入 Actions 密钥和变量:更新插入、创建、更新、删除 |
|
||||||
| actions_run_read | actions | 读取 | 读取 Actions 工作流、运行、作业、日志和构件 |
|
| actions_run_read | actions | 读取 | 读取 Actions 工作流、运行、作业、日志和构件 |
|
||||||
|
|||||||
+2
-2
@@ -155,9 +155,9 @@ Cursor 等客戶端可使用 stdio 命令:
|
|||||||
| issue_read | issue | 讀取 | 讀取問題:詳情、評論或標籤 |
|
| issue_read | issue | 讀取 | 讀取問題:詳情、評論或標籤 |
|
||||||
| issue_write | issue | 寫入 | 寫入問題:創建、更新、管理評論和標籤 |
|
| issue_write | issue | 寫入 | 寫入問題:創建、更新、管理評論和標籤 |
|
||||||
| list_pull_requests | pull_request | 讀取 | 列出倉庫拉取請求 |
|
| list_pull_requests | pull_request | 讀取 | 列出倉庫拉取請求 |
|
||||||
| pull_request_read | pull_request | 讀取 | 讀取拉取請求:詳情、差異、變更檔案、頭部提交狀態、審查 |
|
| pull_request_read | pull_request | 讀取 | 讀取拉取請求:詳情、差異、變更檔案、頭部提交狀態、審查、審查評論 |
|
||||||
| pull_request_write | pull_request | 寫入 | 寫入拉取請求:創建、更新、關閉、重新開啟、合併、更新分支、管理審查者 |
|
| pull_request_write | pull_request | 寫入 | 寫入拉取請求:創建、更新、關閉、重新開啟、合併、更新分支、管理審查者 |
|
||||||
| pull_request_review_write | pull_request | 寫入 | 寫入 PR 審查:創建、提交、刪除、駁回 |
|
| pull_request_review_write | pull_request | 寫入 | 寫入 PR 審查:創建、提交、刪除、駁回、回覆和解決審查評論 |
|
||||||
| actions_config_read | actions | 讀取 | 讀取 Actions 密鑰和變數 |
|
| actions_config_read | actions | 讀取 | 讀取 Actions 密鑰和變數 |
|
||||||
| actions_config_write | actions | 寫入 | 寫入 Actions 密鑰和變數:更新插入、創建、更新、刪除 |
|
| actions_config_write | actions | 寫入 | 寫入 Actions 密鑰和變數:更新插入、創建、更新、刪除 |
|
||||||
| actions_run_read | actions | 讀取 | 讀取 Actions 工作流程、執行、作業、日誌和產物 |
|
| actions_run_read | actions | 讀取 | 讀取 Actions 工作流程、執行、作業、日誌和產物 |
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ go 1.26.0
|
|||||||
|
|
||||||
require (
|
require (
|
||||||
gitea.dev/sdk v1.2.0
|
gitea.dev/sdk v1.2.0
|
||||||
github.com/mark3labs/mcp-go v0.56.0
|
github.com/mark3labs/mcp-go v0.57.0
|
||||||
go.uber.org/zap v1.28.0
|
go.uber.org/zap v1.28.0
|
||||||
go.uber.org/zap/exp v0.3.0
|
go.uber.org/zap/exp v0.3.0
|
||||||
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
gopkg.in/natefinch/lumberjack.v2 v2.2.1
|
||||||
|
|||||||
@@ -22,8 +22,8 @@ github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
|||||||
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
github.com/mark3labs/mcp-go v0.56.0 h1:7aCj2wODCskMi08f923ADG+EfELZBdiKILny415cIS8=
|
github.com/mark3labs/mcp-go v0.57.0 h1:jzWKyCzdWnwnZt05cvcQQ+ngiUl2RnixXJa7Kj4qP1E=
|
||||||
github.com/mark3labs/mcp-go v0.56.0/go.mod h1:+8WclSK1ZUweCP3hvktSji8n8ABG/95QaEkeVE/Uwas=
|
github.com/mark3labs/mcp-go v0.57.0/go.mod h1:+8WclSK1ZUweCP3hvktSji8n8ABG/95QaEkeVE/Uwas=
|
||||||
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.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ=
|
||||||
|
|||||||
+128
-16
@@ -44,13 +44,13 @@ var (
|
|||||||
|
|
||||||
PullRequestReadTool = mcp.NewTool(
|
PullRequestReadTool = mcp.NewTool(
|
||||||
PullRequestReadToolName,
|
PullRequestReadToolName,
|
||||||
mcp.WithDescription("Read pull request: details, diff, changed files, head commit status, reviews."),
|
mcp.WithDescription("Read pull request: details, diff, changed files, head commit status, reviews, review comments."),
|
||||||
mcp.WithToolAnnotation(annotation.ReadOnly("Read pull request details")),
|
mcp.WithToolAnnotation(annotation.ReadOnly("Read pull request details")),
|
||||||
mcp.WithString("method", mcp.Required(), mcp.Enum("get", "get_diff", "get_files", "get_status", "get_reviews", "get_review", "get_review_comments")),
|
mcp.WithString("method", mcp.Required(), mcp.Enum("get", "get_diff", "get_files", "get_status", "get_reviews", "get_review", "get_review_comments")),
|
||||||
mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
|
mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
|
||||||
mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
|
mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
|
||||||
mcp.WithNumber("pull_number", mcp.Required()),
|
mcp.WithNumber("pull_number", mcp.Required()),
|
||||||
mcp.WithNumber("review_id", mcp.Description("for 'get_review'/'get_review_comments'")),
|
mcp.WithNumber("review_id", mcp.Description("for 'get_review'; optional for 'get_review_comments', omit to list all")),
|
||||||
mcp.WithBoolean("binary", mcp.Description("include binary diff")),
|
mcp.WithBoolean("binary", mcp.Description("include binary diff")),
|
||||||
mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1)),
|
mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1)),
|
||||||
mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)),
|
mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)),
|
||||||
@@ -89,15 +89,16 @@ var (
|
|||||||
|
|
||||||
PullRequestReviewWriteTool = mcp.NewTool(
|
PullRequestReviewWriteTool = mcp.NewTool(
|
||||||
PullRequestReviewWriteToolName,
|
PullRequestReviewWriteToolName,
|
||||||
mcp.WithDescription("Write PR reviews: create, submit, delete, dismiss."),
|
mcp.WithDescription("Write PR reviews: create, submit, delete, dismiss, reply to and resolve review comments."),
|
||||||
mcp.WithToolAnnotation(annotation.Write("Submit a pull request review")),
|
mcp.WithToolAnnotation(annotation.Write("Write pull request reviews")),
|
||||||
mcp.WithString("method", mcp.Required(), mcp.Enum("create", "submit", "delete", "dismiss")),
|
mcp.WithString("method", mcp.Required(), mcp.Enum("create", "submit", "delete", "dismiss", "reply_comment", "resolve_thread", "unresolve_thread")),
|
||||||
mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
|
mcp.WithString("owner", mcp.Required(), mcp.Description(params.OwnerDesc)),
|
||||||
mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
|
mcp.WithString("repo", mcp.Required(), mcp.Description(params.RepoDesc)),
|
||||||
mcp.WithNumber("pull_number", mcp.Required()),
|
mcp.WithNumber("pull_number", mcp.Description("required except for 'resolve_thread'/'unresolve_thread'")),
|
||||||
mcp.WithNumber("review_id", mcp.Description("required except for 'create'")),
|
mcp.WithNumber("review_id", mcp.Description("for 'submit'/'delete'/'dismiss'")),
|
||||||
|
mcp.WithNumber("comment_id", mcp.Description("comment ID from 'get_review_comments'; resolve takes the thread's first")),
|
||||||
mcp.WithString("state", mcp.Enum("APPROVED", "REQUEST_CHANGES", "COMMENT", "PENDING")),
|
mcp.WithString("state", mcp.Enum("APPROVED", "REQUEST_CHANGES", "COMMENT", "PENDING")),
|
||||||
mcp.WithString("body"),
|
mcp.WithString("body", mcp.Description("review body, or reply text for 'reply_comment'")),
|
||||||
mcp.WithString("commit_id", mcp.Description("for 'create'")),
|
mcp.WithString("commit_id", mcp.Description("for 'create'")),
|
||||||
mcp.WithString("message", mcp.Description("dismissal reason")),
|
mcp.WithString("message", mcp.Description("dismissal reason")),
|
||||||
mcp.WithArray("comments", mcp.Description("inline comments (for 'create')"), mcp.Items(map[string]any{
|
mcp.WithArray("comments", mcp.Description("inline comments (for 'create')"), mcp.Items(map[string]any{
|
||||||
@@ -257,6 +258,12 @@ func pullRequestReviewWriteFn(ctx context.Context, req mcp.CallToolRequest) (*mc
|
|||||||
return deletePullRequestReviewFn(ctx, req)
|
return deletePullRequestReviewFn(ctx, req)
|
||||||
case "dismiss":
|
case "dismiss":
|
||||||
return dismissPullRequestReviewFn(ctx, req)
|
return dismissPullRequestReviewFn(ctx, req)
|
||||||
|
case "reply_comment":
|
||||||
|
return replyPullRequestReviewCommentFn(ctx, req)
|
||||||
|
case "resolve_thread":
|
||||||
|
return resolveReviewThreadFn(ctx, req)
|
||||||
|
case "unresolve_thread":
|
||||||
|
return unresolveReviewThreadFn(ctx, req)
|
||||||
default:
|
default:
|
||||||
return to.ErrorResult(fmt.Errorf("unknown method: %s", method))
|
return to.ErrorResult(fmt.Errorf("unknown method: %s", method))
|
||||||
}
|
}
|
||||||
@@ -567,19 +574,39 @@ func listPullRequestReviewCommentsFn(ctx context.Context, req mcp.CallToolReques
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return to.ErrorResult(err)
|
return to.ErrorResult(err)
|
||||||
}
|
}
|
||||||
reviewID, err := params.GetIndex(args, "review_id")
|
|
||||||
if err != nil {
|
|
||||||
return to.ErrorResult(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
client, err := gitea.ClientFromContext(ctx)
|
client, err := gitea.ClientFromContext(ctx)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||||
}
|
}
|
||||||
|
|
||||||
comments, _, err := client.PullRequests.ListPullReviewComments(ctx, owner, repo, index, reviewID)
|
// review comments hang off reviews, so without a review_id walk a page of
|
||||||
if err != nil {
|
// reviews, keeping each thread and its replies together
|
||||||
return to.ErrorResult(fmt.Errorf("list review comments for review %v on %v/%v/pr/%v err: %v", reviewID, owner, repo, index, err))
|
var reviewIDs []int64
|
||||||
|
if reviewID := params.GetOptionalInt(args, "review_id", 0); reviewID != 0 {
|
||||||
|
reviewIDs = append(reviewIDs, reviewID)
|
||||||
|
} else {
|
||||||
|
page, pageSize := params.GetPagination(args, 30)
|
||||||
|
reviews, _, err := client.PullRequests.ListPullReviews(ctx, owner, repo, index, gitea_sdk.ListPullReviewsOptions{
|
||||||
|
ListOptions: gitea_sdk.ListOptions{Page: page, PageSize: pageSize},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(fmt.Errorf("list reviews for %v/%v/pr/%v err: %v", owner, repo, index, err))
|
||||||
|
}
|
||||||
|
reviewIDs = make([]int64, 0, len(reviews))
|
||||||
|
for _, review := range reviews {
|
||||||
|
if review.CodeCommentsCount > 0 {
|
||||||
|
reviewIDs = append(reviewIDs, review.ID)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var comments []*gitea_sdk.PullReviewComment
|
||||||
|
for _, reviewID := range reviewIDs {
|
||||||
|
reviewComments, _, err := client.PullRequests.ListPullReviewComments(ctx, owner, repo, index, reviewID)
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(fmt.Errorf("list review comments for review %v on %v/%v/pr/%v err: %v", reviewID, owner, repo, index, err))
|
||||||
|
}
|
||||||
|
comments = append(comments, reviewComments...)
|
||||||
}
|
}
|
||||||
|
|
||||||
return to.TextResult(slimReviewComments(comments))
|
return to.TextResult(slimReviewComments(comments))
|
||||||
@@ -775,6 +802,91 @@ func dismissPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*
|
|||||||
return to.TextResult(successMsg)
|
return to.TextResult(successMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func replyPullRequestReviewCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||||
|
args := req.GetArguments()
|
||||||
|
owner, err := params.GetString(args, "owner")
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(err)
|
||||||
|
}
|
||||||
|
repo, err := params.GetString(args, "repo")
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(err)
|
||||||
|
}
|
||||||
|
index, err := params.GetIndex(args, "pull_number")
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(err)
|
||||||
|
}
|
||||||
|
commentID, err := params.GetIndex(args, "comment_id")
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(err)
|
||||||
|
}
|
||||||
|
body, err := params.GetString(args, "body")
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := gitea.ClientFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
comment, _, err := client.PullRequests.CreatePullReviewCommentReply(ctx, owner, repo, index, commentID, gitea_sdk.CreatePullReviewCommentReplyOptions{
|
||||||
|
Body: body,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(fmt.Errorf("reply to review comment %v on %v/%v/pr/%v err: %v", commentID, owner, repo, index, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return to.TextResult(slimReviewComment(comment))
|
||||||
|
}
|
||||||
|
|
||||||
|
func resolveReviewThreadFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||||
|
return setReviewThreadResolvedFn(ctx, req, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func unresolveReviewThreadFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||||
|
return setReviewThreadResolvedFn(ctx, req, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func setReviewThreadResolvedFn(ctx context.Context, req mcp.CallToolRequest, resolved bool) (*mcp.CallToolResult, error) {
|
||||||
|
args := req.GetArguments()
|
||||||
|
owner, err := params.GetString(args, "owner")
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(err)
|
||||||
|
}
|
||||||
|
repo, err := params.GetString(args, "repo")
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(err)
|
||||||
|
}
|
||||||
|
commentID, err := params.GetIndex(args, "comment_id")
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
client, err := gitea.ClientFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
if resolved {
|
||||||
|
_, err = client.PullRequests.ResolvePullReviewComment(ctx, owner, repo, commentID)
|
||||||
|
} else {
|
||||||
|
_, err = client.PullRequests.UnresolvePullReviewComment(ctx, owner, repo, commentID)
|
||||||
|
}
|
||||||
|
if err != nil {
|
||||||
|
return to.ErrorResult(fmt.Errorf("set resolved=%v on review comment %v in %v/%v err: %v", resolved, commentID, owner, repo, err))
|
||||||
|
}
|
||||||
|
|
||||||
|
successMsg := map[string]any{
|
||||||
|
"message": "Successfully updated review thread",
|
||||||
|
"comment_id": commentID,
|
||||||
|
"resolved": resolved,
|
||||||
|
"repository": fmt.Sprintf("%s/%s", owner, repo),
|
||||||
|
}
|
||||||
|
|
||||||
|
return to.TextResult(successMsg)
|
||||||
|
}
|
||||||
|
|
||||||
func mergePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
func mergePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
|
||||||
args := req.GetArguments()
|
args := req.GetArguments()
|
||||||
owner, err := params.GetString(args, "owner")
|
owner, err := params.GetString(args, "owner")
|
||||||
|
|||||||
@@ -1042,3 +1042,147 @@ func Test_reopenPullRequestFn(t *testing.T) {
|
|||||||
t.Fatalf("expected content in result")
|
t.Fatalf("expected content in result")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// serveStub points the client at a test server that answers the SDK version
|
||||||
|
// probe, leaving every other route to handler.
|
||||||
|
func serveStub(t *testing.T, handler http.HandlerFunc) {
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path == "/api/v1/version" {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
_, _ = w.Write([]byte(`{"version":"1.27.0"}`))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
handler(w, r)
|
||||||
|
}))
|
||||||
|
t.Cleanup(server.Close)
|
||||||
|
|
||||||
|
origHost, origToken := flag.Host, flag.Token
|
||||||
|
flag.Host, flag.Token = server.URL, "test-token"
|
||||||
|
t.Cleanup(func() { flag.Host, flag.Token = origHost, origToken })
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_pullRequestReviewWriteFn_comments(t *testing.T) {
|
||||||
|
const (
|
||||||
|
owner = "octo"
|
||||||
|
repo = "demo"
|
||||||
|
index = 7
|
||||||
|
commentID = 42
|
||||||
|
)
|
||||||
|
|
||||||
|
for _, tc := range []struct {
|
||||||
|
method string
|
||||||
|
path string
|
||||||
|
wantBody string
|
||||||
|
}{
|
||||||
|
{"reply_comment", fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/comments/%d/replies", owner, repo, index, commentID), "sure"},
|
||||||
|
{"resolve_thread", fmt.Sprintf("/api/v1/repos/%s/%s/pulls/comments/%d/resolve", owner, repo, commentID), ""},
|
||||||
|
{"unresolve_thread", fmt.Sprintf("/api/v1/repos/%s/%s/pulls/comments/%d/unresolve", owner, repo, commentID), ""},
|
||||||
|
} {
|
||||||
|
t.Run(tc.method, func(t *testing.T) {
|
||||||
|
var gotPath, gotBody string
|
||||||
|
|
||||||
|
serveStub(t, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
if r.URL.Path != tc.path {
|
||||||
|
t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if r.Method != http.MethodPost {
|
||||||
|
t.Errorf("expected POST method, got %s", r.Method)
|
||||||
|
}
|
||||||
|
gotPath = r.URL.Path
|
||||||
|
var body map[string]any
|
||||||
|
_ = json.NewDecoder(r.Body).Decode(&body)
|
||||||
|
gotBody, _ = body["body"].(string)
|
||||||
|
if tc.wantBody == "" {
|
||||||
|
w.WriteHeader(http.StatusNoContent)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
w.WriteHeader(http.StatusCreated)
|
||||||
|
_, _ = w.Write([]byte(`{"id":43,"body":"sure","path":"main.go","position":3}`))
|
||||||
|
})
|
||||||
|
|
||||||
|
req := mcp.CallToolRequest{
|
||||||
|
Params: mcp.CallToolParams{
|
||||||
|
Arguments: map[string]any{
|
||||||
|
"method": tc.method,
|
||||||
|
"owner": owner,
|
||||||
|
"repo": repo,
|
||||||
|
"pull_number": float64(index),
|
||||||
|
"comment_id": float64(commentID),
|
||||||
|
"body": "sure",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := pullRequestReviewWriteFn(context.Background(), req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("pullRequestReviewWriteFn() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if gotPath != tc.path {
|
||||||
|
t.Errorf("expected request to %s, got %q", tc.path, gotPath)
|
||||||
|
}
|
||||||
|
|
||||||
|
// resolve and unresolve send no body, reply sends the reply text
|
||||||
|
if gotBody != tc.wantBody {
|
||||||
|
t.Errorf("expected body %q, got %q", tc.wantBody, gotBody)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result.Content) == 0 {
|
||||||
|
t.Fatalf("expected content in result")
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func Test_listPullRequestReviewCommentsFn_allReviews(t *testing.T) {
|
||||||
|
const (
|
||||||
|
owner = "octo"
|
||||||
|
repo = "demo"
|
||||||
|
index = 7
|
||||||
|
)
|
||||||
|
|
||||||
|
var gotReviewPaths []string
|
||||||
|
|
||||||
|
serveStub(t, func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
w.Header().Set("Content-Type", "application/json")
|
||||||
|
switch r.URL.Path {
|
||||||
|
case fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews", owner, repo, index):
|
||||||
|
// the middle review has no review comments and must not be fetched
|
||||||
|
_, _ = w.Write([]byte(`[{"id":1,"comments_count":1},{"id":2,"comments_count":0},{"id":3,"comments_count":2}]`))
|
||||||
|
case fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews/1/comments", owner, repo, index),
|
||||||
|
fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d/reviews/3/comments", owner, repo, index):
|
||||||
|
gotReviewPaths = append(gotReviewPaths, r.URL.Path)
|
||||||
|
_, _ = w.Write([]byte(`[{"id":11,"body":"nit","path":"main.go","position":3}]`))
|
||||||
|
default:
|
||||||
|
t.Errorf("unexpected request: %s %s", r.Method, r.URL.Path)
|
||||||
|
w.WriteHeader(http.StatusNotFound)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
req := mcp.CallToolRequest{
|
||||||
|
Params: mcp.CallToolParams{
|
||||||
|
Arguments: map[string]any{
|
||||||
|
"method": "get_review_comments",
|
||||||
|
"owner": owner,
|
||||||
|
"repo": repo,
|
||||||
|
"pull_number": float64(index),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := pullRequestReadFn(context.Background(), req)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("pullRequestReadFn() error = %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(gotReviewPaths) != 2 {
|
||||||
|
t.Errorf("expected comments of 2 reviews to be fetched, got %v", gotReviewPaths)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(result.Content) == 0 {
|
||||||
|
t.Fatalf("expected content in result")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -137,8 +137,9 @@ func slimReviewComment(c *gitea_sdk.PullReviewComment) map[string]any {
|
|||||||
if c == nil {
|
if c == nil {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
return map[string]any{
|
m := map[string]any{
|
||||||
"id": c.ID,
|
"id": c.ID,
|
||||||
|
"review_id": c.ReviewID,
|
||||||
"body": c.Body,
|
"body": c.Body,
|
||||||
"path": c.Path,
|
"path": c.Path,
|
||||||
"position": c.LineNum,
|
"position": c.LineNum,
|
||||||
@@ -149,6 +150,11 @@ func slimReviewComment(c *gitea_sdk.PullReviewComment) map[string]any {
|
|||||||
"created_at": c.Created,
|
"created_at": c.Created,
|
||||||
"updated_at": c.Updated,
|
"updated_at": c.Updated,
|
||||||
}
|
}
|
||||||
|
// the thread's first comment carries the resolver
|
||||||
|
if c.Resolver != nil {
|
||||||
|
m["resolved_by"] = slim.UserLogin(c.Resolver)
|
||||||
|
}
|
||||||
|
return m
|
||||||
}
|
}
|
||||||
|
|
||||||
func slimReviewComments(comments []*gitea_sdk.PullReviewComment) []map[string]any {
|
func slimReviewComments(comments []*gitea_sdk.PullReviewComment) []map[string]any {
|
||||||
|
|||||||
@@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
|
||||||
|
"extends": [
|
||||||
|
"local>gitea/renovate-config"
|
||||||
|
],
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user