Compare commits

..

7 Commits

Author SHA1 Message Date
torben cd7c29271a fix: add missing descriptions for various tools
Fixes interoperability with vscode, Perplexity, Mistral and others.
2026-07-19 08:40:39 +02:00
jermey bbde7ee110 feat(issue): add milestones and type filters to list_issues (#199)
## Summary

`list_issues` could not be scoped to a milestone, and the milestone was
absent from its list output. To find which milestone an issue belongs to,
callers had to read every issue individually — expensive on repositories
with hundreds of issues.

## Changes

- `list_issues`: add `milestones` (name or ID filter) and `type`
  (`issues`/`pulls`) parameters, wiring the SDK's
  `ListIssueOption.Milestones` and `ListIssueOption.Type`.
- Include the `milestone` `{id, title}` field in `slimIssues` (the list
  output), consistent with the existing `slimIssue` (single-issue) output.
- Tests: extend `Test_listRepoIssuesFn_filters` to assert the `milestones`
  and `type` query params, and add `Test_listRepoIssuesFn_includesMilestone`
  for the milestone field in list output.

This is a natural continuation of #164 (which added `labels`/`since`/`before`
to `list_issues`); `milestones` and `type` are supported by the SDK but were
not yet wired through.

## Testing

- `go build ./...` — ok
- `go vet ./operation/issue/` — ok
- `go test ./...` — all pass
- `make fmt` — clean

---
This PR was written with the help of Claude Opus 4.8

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/199
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: jermey <186737+jermey@noreply.gitea.com>
Co-committed-by: jermey <186737+jermey@noreply.gitea.com>
2026-05-31 22:19:41 +00:00
Sandro Santilli 3af6250589 Add example configuration for OpenCode (#198)
It took me some time so it's nice if the next person finds easier instructions here.

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/198
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Sandro Santilli <strk@kbt.io>
Co-committed-by: Sandro Santilli <strk@kbt.io>
2026-05-28 17:46:35 +00:00
Lunny Xiao a35fd26112 replace gitea sdk (#197)
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/197
Reviewed-by: silverwind <2021+silverwind@noreply.gitea.com>
2026-05-28 02:41:43 +00:00
Jason 4d7a33e57e Add a sample config for Mistral's Vibe agent config (#194)
Note: There seems to be some nascent work on
https://github.com/mistralai/mistral-vibe to make it easier to pass
environment variables into the config and eliminate hardcoding. But,
it hasn't landed yet, as far as I can tell.

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/194
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Jason <jro@codegrinder.com>
Co-committed-by: Jason <jro@codegrinder.com>
2026-05-19 17:33:19 +00:00
silverwind 371a06403a Simplify codebase (#195)
Net **-650 LOC** by removing duplication and dead noise. All tests pass.

### Duplication & helpers
- Extracted shared slim helpers (`UserLogin`, `UserLogins`, `LabelNames`, `BodyWithAttachments`, `UserDetail`, `Repo`/`Repos`, `Label`/`Labels`) into `pkg/slim`. Deleted the 4 copies that lived in `issue/`, `pull/`, `search/`, `repo/` (plus duplicate `slimUserDetail`/`slimRepo`/`slimLabels` across packages).
- Added `params.GetOptionalBoolPtr` and `params.GetOptionalStringPtr`. Replaced 18 awkward `new(localVar); if !ok { = nil }` patterns across `repo/`, `pull/`, `issue/`, `label/`, `milestone/`, `search/`.
- Extracted `pullRequestReviewerFn` for the 99%-identical `createPullRequestReviewerFn`/`deletePullRequestReviewerFn` pair.

### Dead code & noise
- Deleted **122** `log.Debugf("Called X")` narration lines (`zap.AddCaller` already records the caller) and pruned 19 unused `log` imports.
- Removed the unused `log.Logger` wrapper; the mcp-go server now uses `log.Default().Sugar()` directly (matches `util.Logger`).
- Deleted dead `s.DeleteTools("")` — confirmed no-op in mcp-go.
- Stripped WHAT-narration comments per project guidance.

### Correctness & consistency
- Fixed `log.Errorf(err.Error())` format-string bug in `pkg/to/to.go` — a `%` in the error would have been interpreted as a directive.
- Standardized `to.TextResult`/`to.ErrorResult` usage; `release.go`, `tag.go`, `branch.go` were bypassing the helpers in 9 sites (skipping the wrapper's debug/error logging).
- Made `params.GetString` reject empty strings; dropped 21 redundant `err != nil || x == ""` checks in `operation/actions/`.
- Replaced raw `args["org"].(string)` in `ListOrgReposFn` with `params.GetString` to match the rest of the codebase.

### Performance
- **Cached `*gitea.Client` by host+token via `sync.Map`** + shared `*http.Transport` via `sync.Once` for both SDK and raw REST paths. Eliminates the SDK's `/api/v1/version` preflight on every tool call and enables connection keep-alive across requests.
- Gated `to.TextResult` debug log behind `flag.Debug` to skip the `string(bytes)` allocation when debug is off.
- Hoisted `8192` and `60s` magic numbers in `pkg/gitea/rest.go` into named constants.

---
This PR was written with the help of Claude Opus 4.7

---------

Co-authored-by: silverwind <silv3rwind@gmail.com>
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/195
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-committed-by: silverwind <me@silverwind.io>
2026-05-19 08:25:36 +00:00
silverwind e36137f5a1 Add org.opencontainers.image.source label (#193)
Two image-label fixes:

- Add `org.opencontainers.image.source` so tools like renovate can retrieve release notes from the source repo.
- Re-declare `ARG VERSION` in the final stage. `ARG` after `FROM` is stage-scoped, so the builder-stage `VERSION` never reached the final stage and `org.opencontainers.image.version` expanded to an empty string in published images.

Closes #192

---
This PR was written with the help of Claude Opus 4.7

Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/193
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-committed-by: silverwind <me@silverwind.io>
2026-05-17 18:10:54 +00:00
53 changed files with 976 additions and 1170 deletions
+3
View File
@@ -22,11 +22,14 @@ RUN --mount=type=cache,target=/go/pkg/mod \
# Final stage # Final stage
FROM gcr.io/distroless/static-debian12:nonroot FROM gcr.io/distroless/static-debian12:nonroot
ARG VERSION=dev
WORKDIR /app WORKDIR /app
COPY --from=builder --chown=nonroot:nonroot /app/gitea-mcp . COPY --from=builder --chown=nonroot:nonroot /app/gitea-mcp .
USER nonroot:nonroot USER nonroot:nonroot
LABEL org.opencontainers.image.version="${VERSION}" LABEL org.opencontainers.image.version="${VERSION}"
LABEL org.opencontainers.image.source="https://gitea.com/gitea/gitea-mcp"
CMD ["/app/gitea-mcp"] CMD ["/app/gitea-mcp"]
+43
View File
@@ -15,6 +15,7 @@
- [🚧 Installation](#-installation) - [🚧 Installation](#-installation)
- [Usage with Claude Code](#usage-with-claude-code) - [Usage with Claude Code](#usage-with-claude-code)
- [Usage with VS Code](#usage-with-vs-code) - [Usage with VS Code](#usage-with-vs-code)
- [Usage with Mistral Vibe](#usage-with-mistral-vibe)
- [📥 Download the official binary release](#-download-the-official-binary-release) - [📥 Download the official binary release](#-download-the-official-binary-release)
- [🔧 Build from Source](#-build-from-source) - [🔧 Build from Source](#-build-from-source)
- [📁 Add to PATH](#-add-to-path) - [📁 Add to PATH](#-add-to-path)
@@ -33,6 +34,23 @@ Model Context Protocol (MCP) is a protocol that allows for the integration of va
## 🚧 Installation ## 🚧 Installation
### Usage with OpenCode (opencode.ai)
Add a snippet like the following in the "mcp" top-level object (add one if you don't have any):
```json
"gitea-mcp": {
"enabled": true,
"type": "local",
"command": [
"gitea-mcp",
"-t", "stdio",
"-H", "https://git.your-domain.org",
"-T", "<your personal access token>"
]
}
```
### Usage with Claude Code ### Usage with Claude Code
This method uses `go run` and requires [Go](https://go.dev) to be installed. This method uses `go run` and requires [Go](https://go.dev) to be installed.
@@ -85,6 +103,31 @@ Optionally, you can add it to a file called `.vscode/mcp.json` in your workspace
} }
``` ```
### Usage with Mistral Vibe
Add the following configuration to your Mistral Vibe MCP configuration file (`~/.vibe/config.toml`):
```toml
[[mcp_servers]]
name = "gitea"
transport = "stdio"
command = "docker"
args = [
"run",
"--rm",
"-i",
"-e",
"GITEA_ACCESS_TOKEN",
"-e",
"GITEA_HOST",
"docker.gitea.com/gitea-mcp-server",
]
[mcp_servers.env]
GITEA_ACCESS_TOKEN = "TOKEN"
GITEA_HOST = "https://gitea.com"
```
### 📥 Download the official binary release ### 📥 Download the official binary release
You can download the official release from [official Gitea MCP binary releases](https://gitea.com/gitea/gitea-mcp/releases). You can download the official release from [official Gitea MCP binary releases](https://gitea.com/gitea/gitea-mcp/releases).
+5 -5
View File
@@ -3,27 +3,27 @@ module gitea.com/gitea/gitea-mcp
go 1.26.0 go 1.26.0
require ( require (
code.gitea.io/sdk/gitea v0.23.2 gitea.dev/sdk v1.0.1
github.com/mark3labs/mcp-go v0.45.0 github.com/mark3labs/mcp-go v0.45.0
go.uber.org/zap v1.27.1 go.uber.org/zap v1.27.1
gopkg.in/natefinch/lumberjack.v2 v2.2.1 gopkg.in/natefinch/lumberjack.v2 v2.2.1
) )
require ( require (
github.com/42wim/httpsig v1.2.3 // indirect github.com/42wim/httpsig v1.2.4 // indirect
github.com/bahlo/generic-list-go v0.2.0 // indirect github.com/bahlo/generic-list-go v0.2.0 // indirect
github.com/buger/jsonparser v1.1.1 // indirect github.com/buger/jsonparser v1.1.1 // 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.8.0 // indirect github.com/hashicorp/go-version v1.9.0 // indirect
github.com/invopop/jsonschema v0.13.0 // indirect github.com/invopop/jsonschema v0.13.0 // indirect
github.com/mailru/easyjson v0.9.1 // indirect github.com/mailru/easyjson v0.9.1 // indirect
github.com/spf13/cast v1.10.0 // indirect github.com/spf13/cast v1.10.0 // indirect
github.com/wk8/go-ordered-map/v2 v2.1.8 // indirect github.com/wk8/go-ordered-map/v2 v2.1.8 // 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.48.0 // indirect golang.org/x/crypto v0.51.0 // indirect
golang.org/x/sys v0.41.0 // indirect golang.org/x/sys v0.44.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect
) )
+14 -14
View File
@@ -1,7 +1,7 @@
code.gitea.io/sdk/gitea v0.23.2 h1:iJB1FDmLegwfwjX8gotBDHdPSbk/ZR8V9VmEJaVsJYg= gitea.dev/sdk v1.0.1 h1:CWXQUQvp2I6YKOWkhYo1Flx2sRNfMK1X9Op4oR2awXs=
code.gitea.io/sdk/gitea v0.23.2/go.mod h1:yyF5+GhljqvA30sRDreoyHILruNiy4ASufugzYg0VHM= gitea.dev/sdk v1.0.1/go.mod h1:jCf5Uzz0Jkb61jxNgMxLOCWwle1J1B2nKdcRtxuK9rY=
github.com/42wim/httpsig v1.2.3 h1:xb0YyWhkYj57SPtfSttIobJUPJZB9as1nsfo7KWVcEs= github.com/42wim/httpsig v1.2.4 h1:mI5bH0nm4xn7K18fo1K3okNDRq8CCJ0KbBYWyA6r8lU=
github.com/42wim/httpsig v1.2.3/go.mod h1:nZq9OlYKDrUBhptd77IHx4/sZZD+IxTBADvAPI9G/EM= github.com/42wim/httpsig v1.2.4/go.mod h1:yKsYfSyTBEohkPik224QPFylmzEBtda/kjyIAJjh3ps=
github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk= github.com/bahlo/generic-list-go v0.2.0 h1:5sz/EEAK+ls5wF+NeqDpk5+iNdMDXrh3z3nPnH1Wvgk=
github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg= github.com/bahlo/generic-list-go v0.2.0/go.mod h1:2KvAjgMlE5NNynlg/5iLrrCCZ2+5xWbdbCW3pNTGyYg=
github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs=
@@ -18,8 +18,8 @@ github.com/google/go-cmp v0.5.9 h1:O2Tfq5qg4qc4AmwVlvv0oLiVAGB7enBSJ2x2DqQFi38=
github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.5.9/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= github.com/hashicorp/go-version v1.9.0 h1:CeOIz6k+LoN3qX9Z0tyQrPtiB1DFYRPfCIBtaXPSCnA=
github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/go-version v1.9.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA=
github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E= github.com/invopop/jsonschema v0.13.0 h1:KvpoAJWEjR3uD9Kbm2HWJmqsEaHt8lBUpd0qHcIi21E=
github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0=
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
@@ -36,8 +36,8 @@ github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZV
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.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY= github.com/spf13/cast v1.10.0 h1:h2x0u2shc1QuLHfxi+cTJvs30+ZAHOGRic8uyGTDWxY=
github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo= github.com/spf13/cast v1.10.0/go.mod h1:jNfB8QC9IA6ZuY2ZjDp0KtFO2LZZlg4S/7bzP6qqeHo=
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc= github.com/wk8/go-ordered-map/v2 v2.1.8 h1:5h/BUHu93oj4gIdvHHHGsScSTMijfx5PeYkE/fJgbpc=
github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw= github.com/wk8/go-ordered-map/v2 v2.1.8/go.mod h1:5nJHM5DyteebpVlHnWMV0rPz6Zp7+xBAnxjb1X5vnTw=
github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4= github.com/yosida95/uritemplate/v3 v3.0.2 h1:Ed3Oyj9yrmi9087+NczuL5BwkIc4wvTb5zIM+UJPGz4=
@@ -51,18 +51,18 @@ go.uber.org/zap v1.27.1/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.48.0 h1:/VRzVqiRSggnhY7gNRxPauEQ5Drw9haKdM0jqfcCFts= golang.org/x/crypto v0.51.0 h1:IBPXwPfKxY7cWQZ38ZCIRPI50YLeevDLlLnyC5wRGTI=
golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVos= golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8=
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.41.0 h1:Ivj+2Cp/ylzLiEU89QhWblYnOE9zerudt9Ftecq2C6k= golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ=
golang.org/x/sys v0.41.0/go.mod h1:OgkHotnGiDImocRcuBABYBEXf8A9a87e/uXjp9XT3ks= golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw=
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.40.0 h1:36e4zGLqU4yhjlmxEaagx2KuYbJq3EwY8K943ZsHcvg= golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4=
golang.org/x/term v0.40.0/go.mod h1:w2P8uVp06p2iyKKuvXIm7N/y0UCRt3UfJTfZ7oOpglM= golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk=
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=
+99 -123
View File
@@ -2,7 +2,6 @@ package actions
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"net/url" "net/url"
"strconv" "strconv"
@@ -10,11 +9,10 @@ import (
"gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/annotation"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "gitea.com/gitea/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "gitea.dev/sdk"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"
) )
@@ -133,17 +131,14 @@ func configWriteFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
} }
} }
// Secret functions
func listRepoActionSecretsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func listRepoActionSecretsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called listRepoActionSecretsFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil || owner == "" { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, err := params.GetString(req.GetArguments(), "repo") repo, err := params.GetString(req.GetArguments(), "repo")
if err != nil || repo == "" { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
page, pageSize := params.GetPagination(req.GetArguments(), 30) page, pageSize := params.GetPagination(req.GetArguments(), 30)
@@ -152,7 +147,7 @@ func listRepoActionSecretsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
} }
secrets, _, err := client.ListRepoActionSecret(owner, repo, gitea_sdk.ListRepoActionSecretOption{ secrets, _, err := client.Actions.ListRepoSecrets(ctx, owner, repo, gitea_sdk.ListRepoActionsSecretOption{
ListOptions: gitea_sdk.ListOptions{Page: page, PageSize: pageSize}, ListOptions: gitea_sdk.ListOptions{Page: page, PageSize: pageSize},
}) })
if err != nil { if err != nil {
@@ -163,22 +158,21 @@ func listRepoActionSecretsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp
} }
func upsertRepoActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func upsertRepoActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called upsertRepoActionSecretFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil || owner == "" { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, err := params.GetString(req.GetArguments(), "repo") repo, err := params.GetString(req.GetArguments(), "repo")
if err != nil || repo == "" { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
name, err := params.GetString(req.GetArguments(), "name") name, err := params.GetString(req.GetArguments(), "name")
if err != nil || name == "" { if err != nil {
return to.ErrorResult(errors.New("name is required")) return to.ErrorResult(err)
} }
data, err := params.GetString(req.GetArguments(), "data") data, err := params.GetString(req.GetArguments(), "data")
if err != nil || data == "" { if err != nil {
return to.ErrorResult(errors.New("data is required")) return to.ErrorResult(err)
} }
description, _ := req.GetArguments()["description"].(string) description, _ := req.GetArguments()["description"].(string)
@@ -186,8 +180,7 @@ func upsertRepoActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mc
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))
} }
resp, err := client.CreateRepoActionSecret(owner, repo, gitea_sdk.CreateSecretOption{ resp, err := client.Actions.CreateRepoSecret(ctx, owner, repo, name, gitea_sdk.CreateOrUpdateSecretOption{
Name: name,
Data: data, Data: data,
Description: description, Description: description,
}) })
@@ -198,25 +191,24 @@ func upsertRepoActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mc
} }
func deleteRepoActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func deleteRepoActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called deleteRepoActionSecretFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil || owner == "" { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, err := params.GetString(req.GetArguments(), "repo") repo, err := params.GetString(req.GetArguments(), "repo")
if err != nil || repo == "" { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
name, err := params.GetString(req.GetArguments(), "name") name, err := params.GetString(req.GetArguments(), "name")
if err != nil || name == "" { if err != nil {
return to.ErrorResult(errors.New("name is required")) 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))
} }
resp, err := client.DeleteRepoActionSecret(owner, repo, name) resp, err := client.Actions.DeleteRepoSecret(ctx, owner, repo, name)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("delete repo action secret err: %v", err)) return to.ErrorResult(fmt.Errorf("delete repo action secret err: %v", err))
} }
@@ -224,10 +216,9 @@ func deleteRepoActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mc
} }
func listOrgActionSecretsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func listOrgActionSecretsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called listOrgActionSecretsFn")
org, err := params.GetString(req.GetArguments(), "org") org, err := params.GetString(req.GetArguments(), "org")
if err != nil || org == "" { if err != nil {
return to.ErrorResult(errors.New("org is required")) return to.ErrorResult(err)
} }
page, pageSize := params.GetPagination(req.GetArguments(), 30) page, pageSize := params.GetPagination(req.GetArguments(), 30)
@@ -236,7 +227,7 @@ func listOrgActionSecretsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
} }
secrets, _, err := client.ListOrgActionSecret(org, gitea_sdk.ListOrgActionSecretOption{ secrets, _, err := client.Actions.ListOrgSecrets(ctx, org, gitea_sdk.ListOrgActionsSecretOption{
ListOptions: gitea_sdk.ListOptions{Page: page, PageSize: pageSize}, ListOptions: gitea_sdk.ListOptions{Page: page, PageSize: pageSize},
}) })
if err != nil { if err != nil {
@@ -247,18 +238,17 @@ func listOrgActionSecretsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.
} }
func upsertOrgActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func upsertOrgActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called upsertOrgActionSecretFn")
org, err := params.GetString(req.GetArguments(), "org") org, err := params.GetString(req.GetArguments(), "org")
if err != nil || org == "" { if err != nil {
return to.ErrorResult(errors.New("org is required")) return to.ErrorResult(err)
} }
name, err := params.GetString(req.GetArguments(), "name") name, err := params.GetString(req.GetArguments(), "name")
if err != nil || name == "" { if err != nil {
return to.ErrorResult(errors.New("name is required")) return to.ErrorResult(err)
} }
data, err := params.GetString(req.GetArguments(), "data") data, err := params.GetString(req.GetArguments(), "data")
if err != nil || data == "" { if err != nil {
return to.ErrorResult(errors.New("data is required")) return to.ErrorResult(err)
} }
description, _ := req.GetArguments()["description"].(string) description, _ := req.GetArguments()["description"].(string)
@@ -266,8 +256,7 @@ func upsertOrgActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mcp
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))
} }
resp, err := client.CreateOrgActionSecret(org, gitea_sdk.CreateSecretOption{ resp, err := client.Actions.CreateOrgSecret(ctx, org, name, gitea_sdk.CreateOrUpdateSecretOption{
Name: name,
Data: data, Data: data,
Description: description, Description: description,
}) })
@@ -278,14 +267,13 @@ func upsertOrgActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mcp
} }
func deleteOrgActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func deleteOrgActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called deleteOrgActionSecretFn")
org, err := params.GetString(req.GetArguments(), "org") org, err := params.GetString(req.GetArguments(), "org")
if err != nil || org == "" { if err != nil {
return to.ErrorResult(errors.New("org is required")) return to.ErrorResult(err)
} }
name, err := params.GetString(req.GetArguments(), "name") name, err := params.GetString(req.GetArguments(), "name")
if err != nil || name == "" { if err != nil {
return to.ErrorResult(errors.New("name is required")) return to.ErrorResult(err)
} }
escapedOrg := url.PathEscape(org) escapedOrg := url.PathEscape(org)
@@ -297,17 +285,14 @@ func deleteOrgActionSecretFn(ctx context.Context, req mcp.CallToolRequest) (*mcp
return to.TextResult(map[string]any{"message": "secret deleted"}) return to.TextResult(map[string]any{"message": "secret deleted"})
} }
// Variable functions
func listRepoActionVariablesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func listRepoActionVariablesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called listRepoActionVariablesFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil || owner == "" { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, err := params.GetString(req.GetArguments(), "repo") repo, err := params.GetString(req.GetArguments(), "repo")
if err != nil || repo == "" { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
page, pageSize := params.GetPagination(req.GetArguments(), 30) page, pageSize := params.GetPagination(req.GetArguments(), 30)
@@ -324,25 +309,24 @@ func listRepoActionVariablesFn(ctx context.Context, req mcp.CallToolRequest) (*m
} }
func getRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func getRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called getRepoActionVariableFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil || owner == "" { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, err := params.GetString(req.GetArguments(), "repo") repo, err := params.GetString(req.GetArguments(), "repo")
if err != nil || repo == "" { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
name, err := params.GetString(req.GetArguments(), "name") name, err := params.GetString(req.GetArguments(), "name")
if err != nil || name == "" { if err != nil {
return to.ErrorResult(errors.New("name is required")) 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))
} }
variable, _, err := client.GetRepoActionVariable(owner, repo, name) variable, _, err := client.Actions.GetRepoVariable(ctx, owner, repo, name)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get repo action variable err: %v", err)) return to.ErrorResult(fmt.Errorf("get repo action variable err: %v", err))
} }
@@ -350,29 +334,28 @@ func getRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp
} }
func createRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func createRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called createRepoActionVariableFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil || owner == "" { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, err := params.GetString(req.GetArguments(), "repo") repo, err := params.GetString(req.GetArguments(), "repo")
if err != nil || repo == "" { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
name, err := params.GetString(req.GetArguments(), "name") name, err := params.GetString(req.GetArguments(), "name")
if err != nil || name == "" { if err != nil {
return to.ErrorResult(errors.New("name is required")) return to.ErrorResult(err)
} }
value, err := params.GetString(req.GetArguments(), "value") value, err := params.GetString(req.GetArguments(), "value")
if err != nil || value == "" { if err != nil {
return to.ErrorResult(errors.New("value is required")) 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))
} }
resp, err := client.CreateRepoActionVariable(owner, repo, name, value) resp, err := client.Actions.CreateRepoVariable(ctx, owner, repo, name, value)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("create repo action variable err: %v", err)) return to.ErrorResult(fmt.Errorf("create repo action variable err: %v", err))
} }
@@ -380,29 +363,28 @@ func createRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*
} }
func updateRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func updateRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called updateRepoActionVariableFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil || owner == "" { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, err := params.GetString(req.GetArguments(), "repo") repo, err := params.GetString(req.GetArguments(), "repo")
if err != nil || repo == "" { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
name, err := params.GetString(req.GetArguments(), "name") name, err := params.GetString(req.GetArguments(), "name")
if err != nil || name == "" { if err != nil {
return to.ErrorResult(errors.New("name is required")) return to.ErrorResult(err)
} }
value, err := params.GetString(req.GetArguments(), "value") value, err := params.GetString(req.GetArguments(), "value")
if err != nil || value == "" { if err != nil {
return to.ErrorResult(errors.New("value is required")) 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))
} }
resp, err := client.UpdateRepoActionVariable(owner, repo, name, value) resp, err := client.Actions.UpdateRepoVariable(ctx, owner, repo, name, value)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("update repo action variable err: %v", err)) return to.ErrorResult(fmt.Errorf("update repo action variable err: %v", err))
} }
@@ -410,25 +392,24 @@ func updateRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*
} }
func deleteRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func deleteRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called deleteRepoActionVariableFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil || owner == "" { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, err := params.GetString(req.GetArguments(), "repo") repo, err := params.GetString(req.GetArguments(), "repo")
if err != nil || repo == "" { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
name, err := params.GetString(req.GetArguments(), "name") name, err := params.GetString(req.GetArguments(), "name")
if err != nil || name == "" { if err != nil {
return to.ErrorResult(errors.New("name is required")) 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))
} }
resp, err := client.DeleteRepoActionVariable(owner, repo, name) resp, err := client.Actions.DeleteRepoVariable(ctx, owner, repo, name)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("delete repo action variable err: %v", err)) return to.ErrorResult(fmt.Errorf("delete repo action variable err: %v", err))
} }
@@ -436,10 +417,9 @@ func deleteRepoActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*
} }
func listOrgActionVariablesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func listOrgActionVariablesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called listOrgActionVariablesFn")
org, err := params.GetString(req.GetArguments(), "org") org, err := params.GetString(req.GetArguments(), "org")
if err != nil || org == "" { if err != nil {
return to.ErrorResult(errors.New("org is required")) return to.ErrorResult(err)
} }
page, pageSize := params.GetPagination(req.GetArguments(), 30) page, pageSize := params.GetPagination(req.GetArguments(), 30)
@@ -447,7 +427,7 @@ func listOrgActionVariablesFn(ctx context.Context, req mcp.CallToolRequest) (*mc
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))
} }
variables, _, err := client.ListOrgActionVariable(org, gitea_sdk.ListOrgActionVariableOption{ variables, _, err := client.Actions.ListOrgVariables(ctx, org, gitea_sdk.ListOrgActionsVariableOption{
ListOptions: gitea_sdk.ListOptions{Page: page, PageSize: pageSize}, ListOptions: gitea_sdk.ListOptions{Page: page, PageSize: pageSize},
}) })
if err != nil { if err != nil {
@@ -457,21 +437,20 @@ func listOrgActionVariablesFn(ctx context.Context, req mcp.CallToolRequest) (*mc
} }
func getOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func getOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called getOrgActionVariableFn")
org, err := params.GetString(req.GetArguments(), "org") org, err := params.GetString(req.GetArguments(), "org")
if err != nil || org == "" { if err != nil {
return to.ErrorResult(errors.New("org is required")) return to.ErrorResult(err)
} }
name, err := params.GetString(req.GetArguments(), "name") name, err := params.GetString(req.GetArguments(), "name")
if err != nil || name == "" { if err != nil {
return to.ErrorResult(errors.New("name is required")) 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))
} }
variable, _, err := client.GetOrgActionVariable(org, name) variable, _, err := client.Actions.GetOrgVariable(ctx, org, name)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get org action variable err: %v", err)) return to.ErrorResult(fmt.Errorf("get org action variable err: %v", err))
} }
@@ -479,18 +458,17 @@ func getOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.
} }
func createOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func createOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called createOrgActionVariableFn")
org, err := params.GetString(req.GetArguments(), "org") org, err := params.GetString(req.GetArguments(), "org")
if err != nil || org == "" { if err != nil {
return to.ErrorResult(errors.New("org is required")) return to.ErrorResult(err)
} }
name, err := params.GetString(req.GetArguments(), "name") name, err := params.GetString(req.GetArguments(), "name")
if err != nil || name == "" { if err != nil {
return to.ErrorResult(errors.New("name is required")) return to.ErrorResult(err)
} }
value, err := params.GetString(req.GetArguments(), "value") value, err := params.GetString(req.GetArguments(), "value")
if err != nil || value == "" { if err != nil {
return to.ErrorResult(errors.New("value is required")) return to.ErrorResult(err)
} }
description, _ := req.GetArguments()["description"].(string) description, _ := req.GetArguments()["description"].(string)
@@ -498,8 +476,7 @@ func createOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*m
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))
} }
resp, err := client.CreateOrgActionVariable(org, gitea_sdk.CreateOrgActionVariableOption{ resp, err := client.Actions.CreateOrgVariable(ctx, org, name, gitea_sdk.CreateActionsVariableOption{
Name: name,
Value: value, Value: value,
Description: description, Description: description,
}) })
@@ -510,18 +487,17 @@ func createOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*m
} }
func updateOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func updateOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called updateOrgActionVariableFn")
org, err := params.GetString(req.GetArguments(), "org") org, err := params.GetString(req.GetArguments(), "org")
if err != nil || org == "" { if err != nil {
return to.ErrorResult(errors.New("org is required")) return to.ErrorResult(err)
} }
name, err := params.GetString(req.GetArguments(), "name") name, err := params.GetString(req.GetArguments(), "name")
if err != nil || name == "" { if err != nil {
return to.ErrorResult(errors.New("name is required")) return to.ErrorResult(err)
} }
value, err := params.GetString(req.GetArguments(), "value") value, err := params.GetString(req.GetArguments(), "value")
if err != nil || value == "" { if err != nil {
return to.ErrorResult(errors.New("value is required")) return to.ErrorResult(err)
} }
description, _ := req.GetArguments()["description"].(string) description, _ := req.GetArguments()["description"].(string)
@@ -529,7 +505,8 @@ func updateOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*m
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))
} }
resp, err := client.UpdateOrgActionVariable(org, name, gitea_sdk.UpdateOrgActionVariableOption{ resp, err := client.Actions.UpdateOrgVariable(ctx, org, name, gitea_sdk.UpdateActionsVariableOption{
Name: name,
Value: value, Value: value,
Description: description, Description: description,
}) })
@@ -540,14 +517,13 @@ func updateOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*m
} }
func deleteOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func deleteOrgActionVariableFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called deleteOrgActionVariableFn")
org, err := params.GetString(req.GetArguments(), "org") org, err := params.GetString(req.GetArguments(), "org")
if err != nil || org == "" { if err != nil {
return to.ErrorResult(errors.New("org is required")) return to.ErrorResult(err)
} }
name, err := params.GetString(req.GetArguments(), "name") name, err := params.GetString(req.GetArguments(), "name")
if err != nil || name == "" { if err != nil {
return to.ErrorResult(errors.New("name is required")) return to.ErrorResult(err)
} }
_, err = gitea.DoJSON(ctx, "DELETE", fmt.Sprintf("orgs/%s/actions/variables/%s", url.PathEscape(org), url.PathEscape(name)), nil, nil, nil) _, err = gitea.DoJSON(ctx, "DELETE", fmt.Sprintf("orgs/%s/actions/variables/%s", url.PathEscape(org), url.PathEscape(name)), nil, nil, nil)
+42 -56
View File
@@ -12,7 +12,6 @@ import (
"gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/annotation"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "gitea.com/gitea/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/to"
@@ -125,14 +124,13 @@ func doJSONWithFallback(ctx context.Context, method string, paths []string, quer
} }
func listRepoActionWorkflowsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func listRepoActionWorkflowsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called listRepoActionWorkflowsFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil || owner == "" { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, err := params.GetString(req.GetArguments(), "repo") repo, err := params.GetString(req.GetArguments(), "repo")
if err != nil || repo == "" { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
page, pageSize := params.GetPagination(req.GetArguments(), 30) page, pageSize := params.GetPagination(req.GetArguments(), 30)
query := url.Values{} query := url.Values{}
@@ -153,18 +151,17 @@ func listRepoActionWorkflowsFn(ctx context.Context, req mcp.CallToolRequest) (*m
} }
func getRepoActionWorkflowFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func getRepoActionWorkflowFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called getRepoActionWorkflowFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil || owner == "" { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, err := params.GetString(req.GetArguments(), "repo") repo, err := params.GetString(req.GetArguments(), "repo")
if err != nil || repo == "" { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
workflowID, err := params.GetString(req.GetArguments(), "workflow_id") workflowID, err := params.GetString(req.GetArguments(), "workflow_id")
if err != nil || workflowID == "" { if err != nil {
return to.ErrorResult(errors.New("workflow_id is required")) return to.ErrorResult(err)
} }
var result any var result any
@@ -181,22 +178,21 @@ func getRepoActionWorkflowFn(ctx context.Context, req mcp.CallToolRequest) (*mcp
} }
func dispatchRepoActionWorkflowFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func dispatchRepoActionWorkflowFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called dispatchRepoActionWorkflowFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil || owner == "" { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, err := params.GetString(req.GetArguments(), "repo") repo, err := params.GetString(req.GetArguments(), "repo")
if err != nil || repo == "" { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
workflowID, err := params.GetString(req.GetArguments(), "workflow_id") workflowID, err := params.GetString(req.GetArguments(), "workflow_id")
if err != nil || workflowID == "" { if err != nil {
return to.ErrorResult(errors.New("workflow_id is required")) return to.ErrorResult(err)
} }
ref, err := params.GetString(req.GetArguments(), "ref") ref, err := params.GetString(req.GetArguments(), "ref")
if err != nil || ref == "" { if err != nil {
return to.ErrorResult(errors.New("ref is required")) return to.ErrorResult(err)
} }
var inputs map[string]any var inputs map[string]any
@@ -231,14 +227,13 @@ func dispatchRepoActionWorkflowFn(ctx context.Context, req mcp.CallToolRequest)
} }
func listRepoActionRunsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func listRepoActionRunsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called listRepoActionRunsFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil || owner == "" { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, err := params.GetString(req.GetArguments(), "repo") repo, err := params.GetString(req.GetArguments(), "repo")
if err != nil || repo == "" { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
page, pageSize := params.GetPagination(req.GetArguments(), 30) page, pageSize := params.GetPagination(req.GetArguments(), 30)
statusFilter, _ := req.GetArguments()["status"].(string) statusFilter, _ := req.GetArguments()["status"].(string)
@@ -264,14 +259,13 @@ func listRepoActionRunsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca
} }
func getRepoActionRunFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func getRepoActionRunFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called getRepoActionRunFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil || owner == "" { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, err := params.GetString(req.GetArguments(), "repo") repo, err := params.GetString(req.GetArguments(), "repo")
if err != nil || repo == "" { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
runID, err := params.GetIndex(req.GetArguments(), "run_id") runID, err := params.GetIndex(req.GetArguments(), "run_id")
if err != nil || runID <= 0 { if err != nil || runID <= 0 {
@@ -292,14 +286,13 @@ func getRepoActionRunFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
} }
func cancelRepoActionRunFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func cancelRepoActionRunFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called cancelRepoActionRunFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil || owner == "" { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, err := params.GetString(req.GetArguments(), "repo") repo, err := params.GetString(req.GetArguments(), "repo")
if err != nil || repo == "" { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
runID, err := params.GetIndex(req.GetArguments(), "run_id") runID, err := params.GetIndex(req.GetArguments(), "run_id")
if err != nil || runID <= 0 { if err != nil || runID <= 0 {
@@ -319,14 +312,13 @@ func cancelRepoActionRunFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.C
} }
func rerunRepoActionRunFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func rerunRepoActionRunFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called rerunRepoActionRunFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil || owner == "" { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, err := params.GetString(req.GetArguments(), "repo") repo, err := params.GetString(req.GetArguments(), "repo")
if err != nil || repo == "" { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
runID, err := params.GetIndex(req.GetArguments(), "run_id") runID, err := params.GetIndex(req.GetArguments(), "run_id")
if err != nil || runID <= 0 { if err != nil || runID <= 0 {
@@ -351,14 +343,13 @@ func rerunRepoActionRunFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca
} }
func listRepoActionJobsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func listRepoActionJobsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called listRepoActionJobsFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil || owner == "" { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, err := params.GetString(req.GetArguments(), "repo") repo, err := params.GetString(req.GetArguments(), "repo")
if err != nil || repo == "" { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
page, pageSize := params.GetPagination(req.GetArguments(), 30) page, pageSize := params.GetPagination(req.GetArguments(), 30)
statusFilter, _ := req.GetArguments()["status"].(string) statusFilter, _ := req.GetArguments()["status"].(string)
@@ -384,14 +375,13 @@ func listRepoActionJobsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca
} }
func listRepoActionRunJobsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func listRepoActionRunJobsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called listRepoActionRunJobsFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil || owner == "" { if err != nil {
return to.ErrorResult(errors.New("owner is required")) return to.ErrorResult(err)
} }
repo, err := params.GetString(req.GetArguments(), "repo") repo, err := params.GetString(req.GetArguments(), "repo")
if err != nil || repo == "" { if err != nil {
return to.ErrorResult(errors.New("repo is required")) return to.ErrorResult(err)
} }
runID, err := params.GetIndex(req.GetArguments(), "run_id") runID, err := params.GetIndex(req.GetArguments(), "run_id")
if err != nil || runID <= 0 { if err != nil || runID <= 0 {
@@ -416,8 +406,6 @@ func listRepoActionRunJobsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp
return to.TextResult(slimActionJobs(result)) return to.TextResult(slimActionJobs(result))
} }
// Log functions (merged from logs.go)
func logPaths(owner, repo string, jobID int64) []string { func logPaths(owner, repo string, jobID int64) []string {
return []string{ return []string{
fmt.Sprintf("repos/%s/%s/actions/jobs/%d/logs", url.PathEscape(owner), url.PathEscape(repo), jobID), fmt.Sprintf("repos/%s/%s/actions/jobs/%d/logs", url.PathEscape(owner), url.PathEscape(repo), jobID),
@@ -473,7 +461,6 @@ func limitBytes(data []byte, maxBytes int) ([]byte, bool) {
} }
func getRepoActionJobLogPreviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func getRepoActionJobLogPreviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called getRepoActionJobLogPreviewFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -508,7 +495,6 @@ func getRepoActionJobLogPreviewFn(ctx context.Context, req mcp.CallToolRequest)
} }
func downloadRepoActionJobLogFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func downloadRepoActionJobLogFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called downloadRepoActionJobLogFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
+42 -52
View File
@@ -7,12 +7,12 @@ import (
"gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/annotation"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "gitea.com/gitea/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/slim"
"gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/to"
"gitea.com/gitea/gitea-mcp/pkg/tool" "gitea.com/gitea/gitea-mcp/pkg/tool"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "gitea.dev/sdk"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"
) )
@@ -40,11 +40,14 @@ const (
var ( var (
ListRepoIssuesTool = mcp.NewTool( ListRepoIssuesTool = mcp.NewTool(
ListRepoIssuesToolName, ListRepoIssuesToolName,
mcp.WithDescription("List issues in a repository (or pull requests, via the 'type' filter), filterable by state, labels, milestones, and update time range."),
mcp.WithToolAnnotation(annotation.ReadOnly("List repository issues")), mcp.WithToolAnnotation(annotation.ReadOnly("List repository issues")),
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.WithString("state", mcp.DefaultString("all")), mcp.WithString("state", mcp.DefaultString("all")),
mcp.WithString("type", mcp.Description("issues or pulls"), mcp.Enum("issues", "pulls")),
mcp.WithArray("labels", mcp.Description("label name filter"), mcp.Items(map[string]any{"type": "string"})), mcp.WithArray("labels", mcp.Description("label name filter"), mcp.Items(map[string]any{"type": "string"})),
mcp.WithArray("milestones", mcp.Description("milestone name or ID filter"), mcp.Items(map[string]any{"type": "string"})),
mcp.WithString("since", mcp.Description("updated after ISO 8601")), mcp.WithString("since", mcp.Description("updated after ISO 8601")),
mcp.WithString("before", mcp.Description("updated before ISO 8601")), mcp.WithString("before", mcp.Description("updated before ISO 8601")),
mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1)), mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1)),
@@ -145,7 +148,6 @@ func issueWriteFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
} }
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")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -164,12 +166,11 @@ func getIssueByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
return to.ErrorResult(fmt.Errorf("get %v/%v/issue/%v err: %v", owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("get %v/%v/issue/%v err: %v", owner, repo, index, err))
} }
m := slimIssue(&issue.Issue) m := slimIssue(&issue.Issue)
m["body"] = bodyWithAttachments(issue.Body, issue.Assets) m["body"] = slim.BodyWithAttachments(issue.Body, issue.Assets)
return to.TextResult(m) return to.TextResult(m)
} }
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")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -183,15 +184,23 @@ func listRepoIssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
state = "all" state = "all"
} }
labels := params.GetStringSlice(req.GetArguments(), "labels") labels := params.GetStringSlice(req.GetArguments(), "labels")
milestones := params.GetStringSlice(req.GetArguments(), "milestones")
page, pageSize := params.GetPagination(req.GetArguments(), 30) page, pageSize := params.GetPagination(req.GetArguments(), 30)
opt := gitea_sdk.ListIssueOption{ opt := gitea_sdk.ListIssueOption{
State: gitea_sdk.StateType(state), State: gitea_sdk.StateType(state),
Labels: labels, Labels: labels,
Milestones: milestones,
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{
Page: page, Page: page,
PageSize: pageSize, PageSize: pageSize,
}, },
} }
switch req.GetArguments()["type"] {
case "issues":
opt.Type = gitea_sdk.IssueTypeIssue
case "pulls":
opt.Type = gitea_sdk.IssueTypePull
}
if t := params.GetOptionalTime(req.GetArguments(), "since"); t != nil { if t := params.GetOptionalTime(req.GetArguments(), "since"); t != nil {
opt.Since = *t opt.Since = *t
} }
@@ -202,7 +211,7 @@ func listRepoIssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
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))
} }
issues, _, err := client.ListRepoIssues(owner, repo, opt) issues, _, err := client.Issues.ListRepoIssues(ctx, owner, repo, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get %v/%v/issues err: %v", owner, repo, err)) return to.ErrorResult(fmt.Errorf("get %v/%v/issues err: %v", owner, repo, err))
} }
@@ -210,7 +219,6 @@ 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")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -248,7 +256,7 @@ func createIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
opt.Ref = ref opt.Ref = ref
} }
opt.Deadline = params.GetOptionalTime(req.GetArguments(), "deadline") opt.Deadline = params.GetOptionalTime(req.GetArguments(), "deadline")
issue, _, err := client.CreateIssue(owner, repo, opt) issue, _, err := client.Issues.CreateIssue(ctx, owner, repo, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("create %v/%v/issue err: %v", owner, repo, err)) return to.ErrorResult(fmt.Errorf("create %v/%v/issue err: %v", owner, repo, err))
} }
@@ -257,7 +265,6 @@ 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")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -281,7 +288,7 @@ func createIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca
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))
} }
issueComment, _, err := client.CreateIssueComment(owner, repo, index, opt) issueComment, _, err := client.Issues.CreateIssueComment(ctx, owner, repo, index, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("create %v/%v/issue/%v/comment err: %v", owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("create %v/%v/issue/%v/comment err: %v", owner, repo, index, err))
} }
@@ -290,7 +297,6 @@ 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")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -304,39 +310,32 @@ func editIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRes
return to.ErrorResult(err) return to.ErrorResult(err)
} }
opt := gitea_sdk.EditIssueOption{} args := req.GetArguments()
opt := gitea_sdk.EditIssueOption{
title, ok := req.GetArguments()["title"].(string) Body: params.GetPresentStringPtr(args, "body"),
if ok { Ref: params.GetPresentStringPtr(args, "ref"),
Assignees: params.GetStringSlice(args, "assignees"),
Deadline: params.GetOptionalTime(args, "deadline"),
RemoveDeadline: params.GetOptionalBoolPtr(args, "remove_deadline"),
}
if title, ok := args["title"].(string); ok {
opt.Title = title opt.Title = title
} }
body, ok := req.GetArguments()["body"].(string) if val, exists := args["milestone"]; exists {
if ok {
opt.Body = new(body)
}
opt.Assignees = params.GetStringSlice(req.GetArguments(), "assignees")
if val, exists := req.GetArguments()["milestone"]; exists {
if milestone, ok := params.ToInt64(val); ok { if milestone, ok := params.ToInt64(val); ok {
opt.Milestone = new(milestone) opt.Milestone = &milestone
} }
} }
state, ok := req.GetArguments()["state"].(string) if state, ok := args["state"].(string); ok {
if ok { s := gitea_sdk.StateType(state)
opt.State = new(gitea_sdk.StateType(state)) opt.State = &s
}
if ref, ok := req.GetArguments()["ref"].(string); ok {
opt.Ref = &ref
}
opt.Deadline = params.GetOptionalTime(req.GetArguments(), "deadline")
if removeDeadline, ok := req.GetArguments()["remove_deadline"].(bool); ok {
opt.RemoveDeadline = &removeDeadline
} }
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))
} }
issue, _, err := client.EditIssue(owner, repo, index, opt) issue, _, err := client.Issues.EditIssue(ctx, owner, repo, index, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("edit %v/%v/issue/%v err: %v", owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("edit %v/%v/issue/%v err: %v", owner, repo, index, err))
} }
@@ -345,7 +344,6 @@ func editIssueFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRes
} }
func editIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func editIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called editIssueCommentFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -369,7 +367,7 @@ func editIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
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))
} }
issueComment, _, err := client.EditIssueComment(owner, repo, commentID, opt) issueComment, _, err := client.Issues.EditIssueComment(ctx, owner, repo, commentID, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("edit %v/%v/issues/comments/%v err: %v", owner, repo, commentID, err)) return to.ErrorResult(fmt.Errorf("edit %v/%v/issues/comments/%v err: %v", owner, repo, commentID, err))
} }
@@ -378,7 +376,6 @@ func editIssueCommentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
} }
func getIssueCommentsByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func getIssueCommentsByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called getIssueCommentsByIndexFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -399,14 +396,13 @@ func getIssueCommentsByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*m
out := make([]map[string]any, 0, len(comments)) out := make([]map[string]any, 0, len(comments))
for i := range comments { for i := range comments {
m := slimComment(&comments[i].Comment) m := slimComment(&comments[i].Comment)
m["body"] = bodyWithAttachments(comments[i].Body, comments[i].Assets) m["body"] = slim.BodyWithAttachments(comments[i].Body, comments[i].Assets)
out = append(out, m) out = append(out, m)
} }
return to.TextResult(out) return to.TextResult(out)
} }
func getIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func getIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called getIssueLabelsFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -424,17 +420,14 @@ func getIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
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))
} }
labels, _, err := client.GetIssueLabels(owner, repo, index, gitea_sdk.ListLabelsOptions{}) labels, _, err := client.Issues.GetIssueLabels(ctx, owner, repo, index, gitea_sdk.ListLabelsOptions{})
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get %v/%v/issues/%v/labels err: %v", owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("get %v/%v/issues/%v/labels err: %v", owner, repo, index, err))
} }
return to.TextResult(slimLabels(labels)) return to.TextResult(slim.Labels(labels))
} }
// Issue label operations (moved from label package)
func addIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func addIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called addIssueLabelsFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -456,15 +449,14 @@ func addIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
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))
} }
issueLabels, _, err := client.AddIssueLabels(owner, repo, index, gitea_sdk.IssueLabelsOption{Labels: labels}) issueLabels, _, err := client.Issues.AddIssueLabels(ctx, owner, repo, index, gitea_sdk.IssueLabelsOption{Labels: labels})
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("add labels to %v/%v/issue/%v err: %v", owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("add labels to %v/%v/issue/%v err: %v", owner, repo, index, err))
} }
return to.TextResult(slimLabels(issueLabels)) return to.TextResult(slim.Labels(issueLabels))
} }
func replaceIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func replaceIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called replaceIssueLabelsFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -486,15 +478,14 @@ func replaceIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca
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))
} }
issueLabels, _, err := client.ReplaceIssueLabels(owner, repo, index, gitea_sdk.IssueLabelsOption{Labels: labels}) issueLabels, _, err := client.Issues.ReplaceIssueLabels(ctx, owner, repo, index, gitea_sdk.IssueLabelsOption{Labels: labels})
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("replace labels on %v/%v/issue/%v err: %v", owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("replace labels on %v/%v/issue/%v err: %v", owner, repo, index, err))
} }
return to.TextResult(slimLabels(issueLabels)) return to.TextResult(slim.Labels(issueLabels))
} }
func clearIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func clearIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called clearIssueLabelsFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -512,7 +503,7 @@ func clearIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
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))
} }
_, err = client.ClearIssueLabels(owner, repo, index) _, err = client.Issues.ClearIssueLabels(ctx, owner, repo, index)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("clear labels on %v/%v/issue/%v err: %v", owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("clear labels on %v/%v/issue/%v err: %v", owner, repo, index, err))
} }
@@ -520,7 +511,6 @@ func clearIssueLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
} }
func removeIssueLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func removeIssueLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called removeIssueLabelFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -542,7 +532,7 @@ func removeIssueLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
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))
} }
_, err = client.DeleteIssueLabel(owner, repo, index, labelID) _, err = client.Issues.DeleteIssueLabel(ctx, owner, repo, index, labelID)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("remove label %v from %v/%v/issue/%v err: %v", labelID, owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("remove label %v from %v/%v/issue/%v err: %v", labelID, owner, repo, index, err))
} }
+55
View File
@@ -65,7 +65,9 @@ func Test_listRepoIssuesFn_filters(t *testing.T) {
Arguments: map[string]any{ Arguments: map[string]any{
"owner": owner, "owner": owner,
"repo": repo, "repo": repo,
"type": "issues",
"labels": []any{"bug", "enhancement"}, "labels": []any{"bug", "enhancement"},
"milestones": []any{"v1.0", "2"},
"since": "2026-01-01T00:00:00Z", "since": "2026-01-01T00:00:00Z",
}, },
}, },
@@ -85,6 +87,59 @@ func Test_listRepoIssuesFn_filters(t *testing.T) {
if !strings.Contains(gotQuery, "since=2026-01-01") { if !strings.Contains(gotQuery, "since=2026-01-01") {
t.Fatalf("expected since query param, got %s", gotQuery) t.Fatalf("expected since query param, got %s", gotQuery)
} }
if !strings.Contains(gotQuery, "milestones=v1.0%2C2") {
t.Fatalf("expected milestones query param, got %s", gotQuery)
}
if !strings.Contains(gotQuery, "type=issues") {
t.Fatalf("expected type query param, got %s", gotQuery)
}
}
func Test_listRepoIssuesFn_includesMilestone(t *testing.T) {
const (
owner = "octo"
repo = "demo"
)
handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
switch r.URL.Path {
case "/api/v1/version":
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"version":"1.12.0"}`))
case fmt.Sprintf("/api/v1/repos/%s/%s", owner, repo):
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"private":false}`))
case fmt.Sprintf("/api/v1/repos/%s/%s/issues", owner, repo):
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`[
{"number": 1, "title": "with milestone", "state": "closed", "milestone": {"id": 5, "title": "v1.0"}},
{"number": 2, "title": "without milestone", "state": "open"}
]`))
default:
http.NotFound(w, r)
}
})
server := httptest.NewServer(handler)
defer server.Close()
origHost, origToken, origVersion := flag.Host, flag.Token, flag.Version
flag.Host, flag.Token, flag.Version = server.URL, "", "test"
defer func() { flag.Host, flag.Token, flag.Version = origHost, origToken, origVersion }()
req := mcp.CallToolRequest{Params: mcp.CallToolParams{Arguments: map[string]any{
"owner": owner, "repo": repo,
}}}
res, err := listRepoIssuesFn(context.Background(), req)
if err != nil {
t.Fatalf("listRepoIssuesFn() error = %v", err)
}
if res.IsError {
t.Fatalf("unexpected error result: %v", res.Content)
}
body := res.Content[0].(mcp.TextContent).Text
if !strings.Contains(body, `"milestone"`) || !strings.Contains(body, `"v1.0"`) {
t.Fatalf("expected milestone in list output, got: %s", body)
}
} }
func Test_createIssueFn_labels(t *testing.T) { func Test_createIssueFn_labels(t *testing.T) {
+14 -77
View File
@@ -1,63 +1,11 @@
package issue package issue
import ( import (
"fmt" "gitea.com/gitea/gitea-mcp/pkg/slim"
"strings"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "gitea.dev/sdk"
) )
func userLogin(u *gitea_sdk.User) string {
if u == nil {
return ""
}
return u.UserName
}
func userLogins(users []*gitea_sdk.User) []string {
if len(users) == 0 {
return nil
}
out := make([]string, 0, len(users))
for _, u := range users {
if u != nil {
out = append(out, u.UserName)
}
}
return out
}
func labelNames(labels []*gitea_sdk.Label) []string {
if len(labels) == 0 {
return nil
}
out := make([]string, 0, len(labels))
for _, l := range labels {
if l != nil {
out = append(out, l.Name)
}
}
return out
}
func bodyWithAttachments(body string, atts []*gitea_sdk.Attachment) string {
links := make([]string, 0, len(atts))
for _, a := range atts {
if a == nil || a.DownloadURL == "" {
continue
}
links = append(links, fmt.Sprintf("[%s](%s)", a.Name, a.DownloadURL))
}
if len(links) == 0 {
return body
}
joined := strings.Join(links, "\n")
if body == "" {
return joined
}
return body + "\n\n" + joined
}
func slimIssue(i *gitea_sdk.Issue) map[string]any { func slimIssue(i *gitea_sdk.Issue) map[string]any {
if i == nil { if i == nil {
return nil return nil
@@ -68,15 +16,15 @@ func slimIssue(i *gitea_sdk.Issue) map[string]any {
"body": i.Body, "body": i.Body,
"state": i.State, "state": i.State,
"html_url": i.HTMLURL, "html_url": i.HTMLURL,
"user": userLogin(i.Poster), "user": slim.UserLogin(i.Poster),
"labels": labelNames(i.Labels), "labels": slim.LabelNames(i.Labels),
"comments": i.Comments, "comments": i.Comments,
"created_at": i.Created, "created_at": i.Created,
"updated_at": i.Updated, "updated_at": i.Updated,
"closed_at": i.Closed, "closed_at": i.Closed,
} }
if len(i.Assignees) > 0 { if len(i.Assignees) > 0 {
m["assignees"] = userLogins(i.Assignees) m["assignees"] = slim.UserLogins(i.Assignees)
} }
if i.Milestone != nil { if i.Milestone != nil {
m["milestone"] = map[string]any{ m["milestone"] = map[string]any{
@@ -107,13 +55,19 @@ func slimIssues(issues []*gitea_sdk.Issue) []map[string]any {
"title": i.Title, "title": i.Title,
"state": i.State, "state": i.State,
"html_url": i.HTMLURL, "html_url": i.HTMLURL,
"user": userLogin(i.Poster), "user": slim.UserLogin(i.Poster),
"comments": i.Comments, "comments": i.Comments,
"created_at": i.Created, "created_at": i.Created,
"updated_at": i.Updated, "updated_at": i.Updated,
} }
if len(i.Labels) > 0 { if len(i.Labels) > 0 {
m["labels"] = labelNames(i.Labels) m["labels"] = slim.LabelNames(i.Labels)
}
if i.Milestone != nil {
m["milestone"] = map[string]any{
"id": i.Milestone.ID,
"title": i.Milestone.Title,
}
} }
if i.Ref != "" { if i.Ref != "" {
m["ref"] = i.Ref m["ref"] = i.Ref
@@ -133,26 +87,9 @@ func slimComment(c *gitea_sdk.Comment) map[string]any {
return map[string]any{ return map[string]any{
"id": c.ID, "id": c.ID,
"body": c.Body, "body": c.Body,
"user": userLogin(c.Poster), "user": slim.UserLogin(c.Poster),
"html_url": c.HTMLURL, "html_url": c.HTMLURL,
"created_at": c.Created, "created_at": c.Created,
"updated_at": c.Updated, "updated_at": c.Updated,
} }
} }
func slimLabels(labels []*gitea_sdk.Label) []map[string]any {
out := make([]map[string]any, 0, len(labels))
for _, l := range labels {
if l == nil {
continue
}
out = append(out, map[string]any{
"id": l.ID,
"name": l.Name,
"color": l.Color,
"description": l.Description,
"exclusive": l.Exclusive,
})
}
return out
}
+1 -24
View File
@@ -3,7 +3,7 @@ package issue
import ( import (
"testing" "testing"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "gitea.dev/sdk"
) )
func TestSlimIssue(t *testing.T) { func TestSlimIssue(t *testing.T) {
@@ -40,29 +40,6 @@ func TestSlimIssue(t *testing.T) {
} }
} }
func TestBodyWithAttachments(t *testing.T) {
atts := []*gitea_sdk.Attachment{
{Name: "shot.png", DownloadURL: "https://example/shot.png"},
{Name: "log.txt", DownloadURL: "https://example/log.txt"},
}
got := bodyWithAttachments("see attached", atts)
want := "see attached\n\n[shot.png](https://example/shot.png)\n[log.txt](https://example/log.txt)"
if got != want {
t.Errorf("got %q, want %q", got, want)
}
if got := bodyWithAttachments("only body", nil); got != "only body" {
t.Errorf("nil attachments should return body unchanged, got %q", got)
}
if got := bodyWithAttachments("", atts); got != "[shot.png](https://example/shot.png)\n[log.txt](https://example/log.txt)" {
t.Errorf("empty body should drop separator, got %q", got)
}
skipped := []*gitea_sdk.Attachment{nil, {Name: "noop", DownloadURL: ""}}
if got := bodyWithAttachments("body", skipped); got != "body" {
t.Errorf("nil/empty-URL attachments should be skipped, got %q", got)
}
}
func TestSlimIssues_ListIsSlimmer(t *testing.T) { func TestSlimIssues_ListIsSlimmer(t *testing.T) {
i := &gitea_sdk.Issue{ i := &gitea_sdk.Issue{
Index: 1, Index: 1,
+30 -51
View File
@@ -6,12 +6,12 @@ import (
"gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/annotation"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "gitea.com/gitea/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/slim"
"gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/to"
"gitea.com/gitea/gitea-mcp/pkg/tool" "gitea.com/gitea/gitea-mcp/pkg/tool"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "gitea.dev/sdk"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"
) )
@@ -108,7 +108,6 @@ func labelWriteFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
} }
func listRepoLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func listRepoLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called listRepoLabelsFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -129,15 +128,14 @@ func listRepoLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
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))
} }
labels, _, err := client.ListRepoLabels(owner, repo, opt) labels, _, err := client.Repositories.ListRepoLabels(ctx, owner, repo, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("list %v/%v/labels err: %v", owner, repo, err)) return to.ErrorResult(fmt.Errorf("list %v/%v/labels err: %v", owner, repo, err))
} }
return to.TextResult(slimLabels(labels)) return to.TextResult(slim.Labels(labels))
} }
func getRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func getRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called getRepoLabelFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -155,15 +153,14 @@ func getRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
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))
} }
label, _, err := client.GetRepoLabel(owner, repo, id) label, _, err := client.Repositories.GetRepoLabel(ctx, owner, repo, id)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get %v/%v/label/%v err: %v", owner, repo, id, err)) return to.ErrorResult(fmt.Errorf("get %v/%v/label/%v err: %v", owner, repo, id, err))
} }
return to.TextResult(slimLabel(label)) return to.TextResult(slim.Label(label))
} }
func createRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func createRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called createRepoLabelFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -195,15 +192,14 @@ func createRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
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))
} }
label, _, err := client.CreateLabel(owner, repo, opt) label, _, err := client.Repositories.CreateLabel(ctx, owner, repo, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("create %v/%v/label err: %v", owner, repo, err)) return to.ErrorResult(fmt.Errorf("create %v/%v/label err: %v", owner, repo, err))
} }
return to.TextResult(slimLabel(label)) return to.TextResult(slim.Label(label))
} }
func editRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func editRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called editRepoLabelFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -217,33 +213,26 @@ func editRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
return to.ErrorResult(err) return to.ErrorResult(err)
} }
opt := gitea_sdk.EditLabelOption{} args := req.GetArguments()
if name, ok := req.GetArguments()["name"].(string); ok { opt := gitea_sdk.EditLabelOption{
opt.Name = new(name) Name: params.GetOptionalStringPtr(args, "name"),
} Color: params.GetOptionalStringPtr(args, "color"),
if color, ok := req.GetArguments()["color"].(string); ok { Description: params.GetPresentStringPtr(args, "description"),
opt.Color = new(color) IsArchived: params.GetOptionalBoolPtr(args, "is_archived"),
}
if description, ok := req.GetArguments()["description"].(string); ok {
opt.Description = new(description)
}
if isArchived, ok := req.GetArguments()["is_archived"].(bool); ok {
opt.IsArchived = &isArchived
} }
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))
} }
label, _, err := client.EditLabel(owner, repo, id, opt) label, _, err := client.Repositories.EditLabel(ctx, owner, repo, id, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("edit %v/%v/label/%v err: %v", owner, repo, id, err)) return to.ErrorResult(fmt.Errorf("edit %v/%v/label/%v err: %v", owner, repo, id, err))
} }
return to.TextResult(slimLabel(label)) return to.TextResult(slim.Label(label))
} }
func deleteRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func deleteRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called deleteRepoLabelFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -261,7 +250,7 @@ func deleteRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
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))
} }
_, err = client.DeleteLabel(owner, repo, id) _, err = client.Repositories.DeleteLabel(ctx, owner, repo, id)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("delete %v/%v/label/%v err: %v", owner, repo, id, err)) return to.ErrorResult(fmt.Errorf("delete %v/%v/label/%v err: %v", owner, repo, id, err))
} }
@@ -269,7 +258,6 @@ func deleteRepoLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
} }
func listOrgLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func listOrgLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called listOrgLabelsFn")
org, err := params.GetString(req.GetArguments(), "org") org, err := params.GetString(req.GetArguments(), "org")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -286,15 +274,14 @@ func listOrgLabelsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
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))
} }
labels, _, err := client.ListOrgLabels(org, opt) labels, _, err := client.Organizations.ListOrgLabels(ctx, org, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("list %v/labels err: %v", org, err)) return to.ErrorResult(fmt.Errorf("list %v/labels err: %v", org, err))
} }
return to.TextResult(slimLabels(labels)) return to.TextResult(slim.Labels(labels))
} }
func createOrgLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func createOrgLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called createOrgLabelFn")
org, err := params.GetString(req.GetArguments(), "org") org, err := params.GetString(req.GetArguments(), "org")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -321,15 +308,14 @@ func createOrgLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
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))
} }
label, _, err := client.CreateOrgLabel(org, opt) label, _, err := client.Organizations.CreateOrgLabel(ctx, org, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("create %v/labels err: %v", org, err)) return to.ErrorResult(fmt.Errorf("create %v/labels err: %v", org, err))
} }
return to.TextResult(slimLabel(label)) return to.TextResult(slim.Label(label))
} }
func editOrgLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func editOrgLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called editOrgLabelFn")
org, err := params.GetString(req.GetArguments(), "org") org, err := params.GetString(req.GetArguments(), "org")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -339,33 +325,26 @@ func editOrgLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
return to.ErrorResult(err) return to.ErrorResult(err)
} }
opt := gitea_sdk.EditOrgLabelOption{} args := req.GetArguments()
if name, ok := req.GetArguments()["name"].(string); ok { opt := gitea_sdk.EditOrgLabelOption{
opt.Name = new(name) Name: params.GetOptionalStringPtr(args, "name"),
} Color: params.GetOptionalStringPtr(args, "color"),
if color, ok := req.GetArguments()["color"].(string); ok { Description: params.GetPresentStringPtr(args, "description"),
opt.Color = new(color) Exclusive: params.GetOptionalBoolPtr(args, "exclusive"),
}
if description, ok := req.GetArguments()["description"].(string); ok {
opt.Description = new(description)
}
if exclusive, ok := req.GetArguments()["exclusive"].(bool); ok {
opt.Exclusive = new(exclusive)
} }
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))
} }
label, _, err := client.EditOrgLabel(org, id, opt) label, _, err := client.Organizations.EditOrgLabel(ctx, org, id, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("edit %v/labels/%v err: %v", org, id, err)) return to.ErrorResult(fmt.Errorf("edit %v/labels/%v err: %v", org, id, err))
} }
return to.TextResult(slimLabel(label)) return to.TextResult(slim.Label(label))
} }
func deleteOrgLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func deleteOrgLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called deleteOrgLabelFn")
org, err := params.GetString(req.GetArguments(), "org") org, err := params.GetString(req.GetArguments(), "org")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -379,7 +358,7 @@ func deleteOrgLabelFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
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))
} }
_, err = client.DeleteOrgLabel(org, id) _, err = client.Organizations.DeleteOrgLabel(ctx, org, id)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("delete %v/labels/%v err: %v", org, id, err)) return to.ErrorResult(fmt.Errorf("delete %v/labels/%v err: %v", org, id, err))
} }
-25
View File
@@ -1,26 +1 @@
package label package label
import (
gitea_sdk "code.gitea.io/sdk/gitea"
)
func slimLabel(l *gitea_sdk.Label) map[string]any {
if l == nil {
return nil
}
return map[string]any{
"id": l.ID,
"name": l.Name,
"color": l.Color,
"description": l.Description,
"exclusive": l.Exclusive,
}
}
func slimLabels(labels []*gitea_sdk.Label) []map[string]any {
out := make([]map[string]any, 0, len(labels))
for _, l := range labels {
out = append(out, slimLabel(l))
}
return out
}
-25
View File
@@ -1,25 +0,0 @@
package label
import (
"testing"
gitea_sdk "code.gitea.io/sdk/gitea"
)
func TestSlimLabel(t *testing.T) {
l := &gitea_sdk.Label{
ID: 1,
Name: "bug",
Color: "#d73a4a",
Description: "Something isn't working",
Exclusive: false,
}
m := slimLabel(l)
if m["name"] != "bug" {
t.Errorf("expected name bug, got %v", m["name"])
}
if m["color"] != "#d73a4a" {
t.Errorf("expected color, got %v", m["color"])
}
}
+15 -24
View File
@@ -6,12 +6,11 @@ import (
"gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/annotation"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "gitea.com/gitea/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/to"
"gitea.com/gitea/gitea-mcp/pkg/tool" "gitea.com/gitea/gitea-mcp/pkg/tool"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "gitea.dev/sdk"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"
) )
@@ -99,7 +98,6 @@ func milestoneWriteFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
} }
func getMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func getMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called getMilestoneFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -116,7 +114,7 @@ func getMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
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))
} }
milestone, _, err := client.GetMilestone(owner, repo, id) milestone, _, err := client.Repositories.GetMilestone(ctx, owner, repo, id)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get %v/%v/milestone/%v err: %v", owner, repo, id, err)) return to.ErrorResult(fmt.Errorf("get %v/%v/milestone/%v err: %v", owner, repo, id, err))
} }
@@ -125,7 +123,6 @@ func getMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
} }
func listMilestonesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func listMilestonesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called listMilestonesFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -149,7 +146,7 @@ func listMilestonesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
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))
} }
milestones, _, err := client.ListRepoMilestones(owner, repo, opt) milestones, _, err := client.Repositories.ListMilestones(ctx, owner, repo, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get %v/%v/milestones err: %v", owner, repo, err)) return to.ErrorResult(fmt.Errorf("get %v/%v/milestones err: %v", owner, repo, err))
} }
@@ -157,7 +154,6 @@ func listMilestonesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
} }
func createMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func createMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called createMilestoneFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -185,7 +181,7 @@ func createMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
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))
} }
milestone, _, err := client.CreateMilestone(owner, repo, opt) milestone, _, err := client.Repositories.CreateMilestone(ctx, owner, repo, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("create %v/%v/milestone err: %v", owner, repo, err)) return to.ErrorResult(fmt.Errorf("create %v/%v/milestone err: %v", owner, repo, err))
} }
@@ -194,7 +190,6 @@ func createMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
} }
func editMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func editMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called editMilestoneFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -208,27 +203,24 @@ func editMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
return to.ErrorResult(err) return to.ErrorResult(err)
} }
opt := gitea_sdk.EditMilestoneOption{} args := req.GetArguments()
opt := gitea_sdk.EditMilestoneOption{
title, ok := req.GetArguments()["title"].(string) Description: params.GetPresentStringPtr(args, "description"),
if ok { Deadline: params.GetOptionalTime(args, "due_on"),
}
if title, ok := args["title"].(string); ok {
opt.Title = title opt.Title = title
} }
description, ok := req.GetArguments()["description"].(string) if state, ok := args["state"].(string); ok {
if ok { s := gitea_sdk.StateType(state)
opt.Description = new(description) opt.State = &s
} }
state, ok := req.GetArguments()["state"].(string)
if ok {
opt.State = new(gitea_sdk.StateType(state))
}
opt.Deadline = params.GetOptionalTime(req.GetArguments(), "due_on")
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))
} }
milestone, _, err := client.EditMilestone(owner, repo, id, opt) milestone, _, err := client.Repositories.EditMilestone(ctx, owner, repo, id, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("edit %v/%v/milestone/%v err: %v", owner, repo, id, err)) return to.ErrorResult(fmt.Errorf("edit %v/%v/milestone/%v err: %v", owner, repo, id, err))
} }
@@ -237,7 +229,6 @@ func editMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
} }
func deleteMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func deleteMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called deleteMilestoneFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -254,7 +245,7 @@ func deleteMilestoneFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
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))
} }
_, err = client.DeleteMilestone(owner, repo, id) _, err = client.Repositories.DeleteMilestone(ctx, owner, repo, id)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("delete %v/%v/milestone/%v err: %v", owner, repo, id, err)) return to.ErrorResult(fmt.Errorf("delete %v/%v/milestone/%v err: %v", owner, repo, id, err))
} }
+1 -1
View File
@@ -1,7 +1,7 @@
package milestone package milestone
import ( import (
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "gitea.dev/sdk"
) )
func slimMilestone(m *gitea_sdk.Milestone) map[string]any { func slimMilestone(m *gitea_sdk.Milestone) map[string]any {
+9 -14
View File
@@ -7,12 +7,11 @@ import (
"gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/annotation"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "gitea.com/gitea/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/to"
"gitea.com/gitea/gitea-mcp/pkg/tool" "gitea.com/gitea/gitea-mcp/pkg/tool"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "gitea.dev/sdk"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"
) )
@@ -97,7 +96,6 @@ func notificationWriteFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Cal
} }
func listNotificationsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func listNotificationsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called listNotificationsFn")
args := req.GetArguments() args := req.GetArguments()
page, pageSize := params.GetPagination(args, 30) page, pageSize := params.GetPagination(args, 30)
opt := gitea_sdk.ListNotificationOptions{ opt := gitea_sdk.ListNotificationOptions{
@@ -107,10 +105,10 @@ func listNotificationsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Cal
}, },
} }
if status, ok := args["status"].(string); ok { if status, ok := args["status"].(string); ok {
opt.Status = []gitea_sdk.NotifyStatus{gitea_sdk.NotifyStatus(status)} opt.Status = []gitea_sdk.NotificationStatus{gitea_sdk.NotificationStatus(status)}
} }
if subjectType, ok := args["subject_type"].(string); ok { if subjectType, ok := args["subject_type"].(string); ok {
opt.SubjectTypes = []gitea_sdk.NotifySubjectType{gitea_sdk.NotifySubjectType(subjectType)} opt.SubjectTypes = []gitea_sdk.NotificationSubjectType{gitea_sdk.NotificationSubjectType(subjectType)}
} }
if t := params.GetOptionalTime(args, "since"); t != nil { if t := params.GetOptionalTime(args, "since"); t != nil {
opt.Since = *t opt.Since = *t
@@ -127,14 +125,14 @@ func listNotificationsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Cal
owner := params.GetOptionalString(args, "owner", "") owner := params.GetOptionalString(args, "owner", "")
repo := params.GetOptionalString(args, "repo", "") repo := params.GetOptionalString(args, "repo", "")
if owner != "" && repo != "" { if owner != "" && repo != "" {
threads, _, err := client.ListRepoNotifications(owner, repo, opt) threads, _, err := client.Notifications.ListByRepo(ctx, owner, repo, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("list %v/%v/notifications err: %v", owner, repo, err)) return to.ErrorResult(fmt.Errorf("list %v/%v/notifications err: %v", owner, repo, err))
} }
return to.TextResult(slimThreads(threads)) return to.TextResult(slimThreads(threads))
} }
threads, _, err := client.ListNotifications(opt) threads, _, err := client.Notifications.List(ctx, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("list notifications err: %v", err)) return to.ErrorResult(fmt.Errorf("list notifications err: %v", err))
} }
@@ -142,7 +140,6 @@ func listNotificationsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Cal
} }
func getNotificationFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func getNotificationFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called getNotificationFn")
id, err := params.GetIndex(req.GetArguments(), "id") id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -151,7 +148,7 @@ func getNotificationFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
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))
} }
thread, _, err := client.GetNotification(id) thread, _, err := client.Notifications.GetByID(ctx, id)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get notification/%v err: %v", id, err)) return to.ErrorResult(fmt.Errorf("get notification/%v err: %v", id, err))
} }
@@ -159,7 +156,6 @@ func getNotificationFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
} }
func markNotificationReadFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func markNotificationReadFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called markNotificationReadFn")
id, err := params.GetIndex(req.GetArguments(), "id") id, err := params.GetIndex(req.GetArguments(), "id")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -168,7 +164,7 @@ func markNotificationReadFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.
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))
} }
thread, _, err := client.ReadNotification(id) thread, _, err := client.Notifications.MarkReadByID(ctx, id)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("mark notification/%v read err: %v", id, err)) return to.ErrorResult(fmt.Errorf("mark notification/%v read err: %v", id, err))
} }
@@ -179,7 +175,6 @@ func markNotificationReadFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.
} }
func markAllNotificationsReadFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func markAllNotificationsReadFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called markAllNotificationsReadFn")
args := req.GetArguments() args := req.GetArguments()
lastReadAt := time.Now() lastReadAt := time.Now()
if t := params.GetOptionalTime(args, "last_read_at"); t != nil { if t := params.GetOptionalTime(args, "last_read_at"); t != nil {
@@ -197,7 +192,7 @@ func markAllNotificationsReadFn(ctx context.Context, req mcp.CallToolRequest) (*
owner := params.GetOptionalString(args, "owner", "") owner := params.GetOptionalString(args, "owner", "")
repo := params.GetOptionalString(args, "repo", "") repo := params.GetOptionalString(args, "repo", "")
if owner != "" && repo != "" { if owner != "" && repo != "" {
threads, _, err := client.ReadRepoNotifications(owner, repo, opt) threads, _, err := client.Notifications.MarkReadByRepo(ctx, owner, repo, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("mark %v/%v/notifications read err: %v", owner, repo, err)) return to.ErrorResult(fmt.Errorf("mark %v/%v/notifications read err: %v", owner, repo, err))
} }
@@ -207,7 +202,7 @@ func markAllNotificationsReadFn(ctx context.Context, req mcp.CallToolRequest) (*
return to.TextResult("All repository notifications marked as read") return to.TextResult("All repository notifications marked as read")
} }
threads, _, err := client.ReadNotifications(opt) threads, _, err := client.Notifications.MarkRead(ctx, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("mark all notifications read err: %v", err)) return to.ErrorResult(fmt.Errorf("mark all notifications read err: %v", err))
} }
+1 -1
View File
@@ -1,7 +1,7 @@
package notification package notification
import ( import (
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "gitea.dev/sdk"
) )
func slimThread(t *gitea_sdk.NotificationThread) map[string]any { func slimThread(t *gitea_sdk.NotificationThread) map[string]any {
+1 -2
View File
@@ -46,7 +46,6 @@ func RegisterTool(s *server.MCPServer) {
for _, t := range domainTools { for _, t := range domainTools {
s.AddTools(t.Tools()...) s.AddTools(t.Tools()...)
} }
s.DeleteTools("")
tool.WarnUnmatchedAllowedTools(domainTools...) tool.WarnUnmatchedAllowedTools(domainTools...)
} }
@@ -97,7 +96,7 @@ func Run() error {
case "http": case "http":
httpServer := server.NewStreamableHTTPServer( httpServer := server.NewStreamableHTTPServer(
mcpServer, mcpServer,
server.WithLogger(log.New()), server.WithLogger(log.Default().Sugar()),
server.WithHeartbeatInterval(30*time.Second), server.WithHeartbeatInterval(30*time.Second),
server.WithHTTPContextFunc(getContextWithToken), server.WithHTTPContextFunc(getContextWithToken),
) )
+28
View File
@@ -2,8 +2,36 @@ package operation
import ( import (
"testing" "testing"
"gitea.com/gitea/gitea-mcp/pkg/flag"
) )
// TestAllToolsHaveDescriptions ensures every registered tool sets a non-empty
// Tool.Description. mcp-go only serializes the "description" field of a tool
// when it is non-empty, so an omitted description makes strict MCP clients
// (e.g. mcp-probe) reject the tools/list response with "missing field
// `description`".
func TestAllToolsHaveDescriptions(t *testing.T) {
origRO, origAllow := flag.ReadOnly, flag.AllowedTools
t.Cleanup(func() {
flag.ReadOnly, flag.AllowedTools = origRO, origAllow
})
flag.ReadOnly = false
flag.AllowedTools = nil
var missing []string
for _, d := range domainTools {
for _, st := range d.Tools() {
if st.Tool.Description == "" {
missing = append(missing, st.Tool.Name)
}
}
}
if len(missing) > 0 {
t.Errorf("tools missing a description: %v", missing)
}
}
func TestParseAuthToken(t *testing.T) { func TestParseAuthToken(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
-5
View File
@@ -9,7 +9,6 @@ import (
"gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/annotation"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "gitea.com/gitea/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/to"
"gitea.com/gitea/gitea-mcp/pkg/tool" "gitea.com/gitea/gitea-mcp/pkg/tool"
@@ -110,7 +109,6 @@ func escapePackageName(name string) string {
} }
func listPackagesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func listPackagesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called listPackagesFn")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -138,7 +136,6 @@ func listPackagesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
} }
func listPackageVersionsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func listPackageVersionsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called listPackageVersionsFn")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -168,7 +165,6 @@ func listPackageVersionsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.C
} }
func getPackageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func getPackageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called getPackageFn")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -197,7 +193,6 @@ func getPackageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
} }
func deletePackageVersionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func deletePackageVersionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called deletePackageVersionFn")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
+43 -98
View File
@@ -10,10 +10,11 @@ import (
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log" "gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "gitea.com/gitea/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/slim"
"gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/to"
"gitea.com/gitea/gitea-mcp/pkg/tool" "gitea.com/gitea/gitea-mcp/pkg/tool"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "gitea.dev/sdk"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"
) )
@@ -30,6 +31,7 @@ const (
var ( var (
ListRepoPullRequestsTool = mcp.NewTool( ListRepoPullRequestsTool = mcp.NewTool(
ListRepoPullRequestsToolName, ListRepoPullRequestsToolName,
mcp.WithDescription("List pull requests in a repository, filterable by state and milestone, with configurable sort order (e.g. recently updated, most commented)."),
mcp.WithToolAnnotation(annotation.ReadOnly("List pull requests")), mcp.WithToolAnnotation(annotation.ReadOnly("List pull requests")),
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)),
@@ -201,7 +203,7 @@ func closePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
} }
state := gitea_sdk.StateClosed state := gitea_sdk.StateClosed
pr, _, err := client.EditPullRequest(owner, repo, index, gitea_sdk.EditPullRequestOption{ pr, _, err := client.PullRequests.EditPullRequest(ctx, owner, repo, index, gitea_sdk.EditPullRequestOption{
State: &state, State: &state,
}) })
if err != nil { if err != nil {
@@ -231,7 +233,7 @@ func reopenPullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Cal
} }
state := gitea_sdk.StateOpen state := gitea_sdk.StateOpen
pr, _, err := client.EditPullRequest(owner, repo, index, gitea_sdk.EditPullRequestOption{ pr, _, err := client.PullRequests.EditPullRequest(ctx, owner, repo, index, gitea_sdk.EditPullRequestOption{
State: &state, State: &state,
}) })
if err != nil { if err != nil {
@@ -261,7 +263,6 @@ func pullRequestReviewWriteFn(ctx context.Context, req mcp.CallToolRequest) (*mc
} }
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")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -279,7 +280,7 @@ func getPullRequestByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp
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))
} }
pr, _, err := client.GetPullRequest(owner, repo, index) pr, _, err := client.PullRequests.GetPullRequest(ctx, owner, repo, index)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get %v/%v/pr/%v err: %v", owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("get %v/%v/pr/%v err: %v", owner, repo, index, err))
} }
@@ -293,12 +294,11 @@ func getPullRequestByIndexFn(ctx context.Context, req mcp.CallToolRequest) (*mcp
} }
m := slimPullRequest(pr) m := slimPullRequest(pr)
m["body"] = bodyWithAttachments(pr.Body, assets) m["body"] = slim.BodyWithAttachments(pr.Body, assets)
return to.TextResult(m) return to.TextResult(m)
} }
func getPullRequestDiffFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func getPullRequestDiffFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called getPullRequestDiffFn")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -318,7 +318,7 @@ func getPullRequestDiffFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca
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))
} }
diffBytes, _, err := client.GetPullRequestDiff(owner, repo, index, gitea_sdk.PullRequestDiffOptions{ diffBytes, _, err := client.PullRequests.GetPullRequestDiff(ctx, owner, repo, index, gitea_sdk.PullRequestDiffOptions{
Binary: binary, Binary: binary,
}) })
if err != nil { if err != nil {
@@ -329,7 +329,6 @@ func getPullRequestDiffFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca
} }
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")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -356,7 +355,7 @@ func listRepoPullRequestsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.
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))
} }
pullRequests, _, err := client.ListRepoPullRequests(owner, repo, opt) pullRequests, _, err := client.PullRequests.ListRepoPullRequests(ctx, owner, repo, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("list %v/%v/pull_requests err: %v", owner, repo, err)) return to.ErrorResult(fmt.Errorf("list %v/%v/pull_requests err: %v", owner, repo, err))
} }
@@ -387,7 +386,6 @@ func applyDraftPrefix(title string, isDraft bool) string {
} }
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")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -432,7 +430,7 @@ func createPullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Cal
opt.Labels = labelIDs opt.Labels = labelIDs
} }
opt.Deadline = params.GetOptionalTime(args, "deadline") opt.Deadline = params.GetOptionalTime(args, "deadline")
pr, _, err := client.CreatePullRequest(owner, repo, opt) pr, _, err := client.PullRequests.CreatePullRequest(ctx, owner, repo, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("create %v/%v/pull_request err: %v", owner, repo, err)) return to.ErrorResult(fmt.Errorf("create %v/%v/pull_request err: %v", owner, repo, err))
} }
@@ -440,8 +438,9 @@ func createPullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Cal
return to.TextResult(slimPullRequest(pr)) return to.TextResult(slimPullRequest(pr))
} }
func createPullRequestReviewerFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { type reviewerOp func(client *gitea_sdk.PullRequestsService, ctx context.Context, owner, repo string, index int64, opt gitea_sdk.PullReviewRequestOptions) (*gitea_sdk.Response, error)
log.Debugf("Called createPullRequestReviewerFn")
func pullRequestReviewerFn(ctx context.Context, req mcp.CallToolRequest, verb string, op reviewerOp) (*mcp.CallToolResult, error) {
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -464,70 +463,31 @@ func createPullRequestReviewerFn(ctx context.Context, req mcp.CallToolRequest) (
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
} }
_, err = client.CreateReviewRequests(owner, repo, index, gitea_sdk.PullReviewRequestOptions{ if _, err := op(client.PullRequests, ctx, owner, repo, index, gitea_sdk.PullReviewRequestOptions{
Reviewers: reviewers, Reviewers: reviewers,
TeamReviewers: teamReviewers, TeamReviewers: teamReviewers,
}) }); err != nil {
if err != nil { return to.ErrorResult(fmt.Errorf("%s review requests for %v/%v/pr/%v err: %v", verb, owner, repo, index, err))
return to.ErrorResult(fmt.Errorf("create review requests for %v/%v/pr/%v err: %v", owner, repo, index, err))
} }
successMsg := map[string]any{ return to.TextResult(map[string]any{
"message": "Successfully created review requests", "message": fmt.Sprintf("Successfully %sd review requests", verb),
"reviewers": reviewers, "reviewers": reviewers,
"team_reviewers": teamReviewers, "team_reviewers": teamReviewers,
"pr_index": index, "pr_index": index,
"repository": fmt.Sprintf("%s/%s", owner, repo), "repository": fmt.Sprintf("%s/%s", owner, repo),
})
} }
return to.TextResult(successMsg) func createPullRequestReviewerFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
return pullRequestReviewerFn(ctx, req, "create", (*gitea_sdk.PullRequestsService).CreateReviewRequests)
} }
func deletePullRequestReviewerFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func deletePullRequestReviewerFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called deletePullRequestReviewerFn") return pullRequestReviewerFn(ctx, req, "delete", (*gitea_sdk.PullRequestsService).DeleteReviewRequests)
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)
}
reviewers := params.GetStringSlice(args, "reviewers")
teamReviewers := params.GetStringSlice(args, "team_reviewers")
client, err := gitea.ClientFromContext(ctx)
if err != nil {
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
}
_, err = client.DeleteReviewRequests(owner, repo, index, gitea_sdk.PullReviewRequestOptions{
Reviewers: reviewers,
TeamReviewers: teamReviewers,
})
if err != nil {
return to.ErrorResult(fmt.Errorf("delete review requests for %v/%v/pr/%v err: %v", owner, repo, index, err))
}
successMsg := map[string]any{
"message": "Successfully deleted review requests",
"reviewers": reviewers,
"team_reviewers": teamReviewers,
"pr_index": index,
"repository": fmt.Sprintf("%s/%s", owner, repo),
}
return to.TextResult(successMsg)
} }
func listPullRequestReviewsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func listPullRequestReviewsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called listPullRequestReviewsFn")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -548,7 +508,7 @@ func listPullRequestReviewsFn(ctx context.Context, req mcp.CallToolRequest) (*mc
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
} }
reviews, _, err := client.ListPullReviews(owner, repo, index, gitea_sdk.ListPullReviewsOptions{ reviews, _, err := client.PullRequests.ListPullReviews(ctx, owner, repo, index, gitea_sdk.ListPullReviewsOptions{
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{
Page: page, Page: page,
PageSize: pageSize, PageSize: pageSize,
@@ -562,7 +522,6 @@ func listPullRequestReviewsFn(ctx context.Context, req mcp.CallToolRequest) (*mc
} }
func getPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func getPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called getPullRequestReviewFn")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -586,7 +545,7 @@ func getPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
} }
review, _, err := client.GetPullReview(owner, repo, index, reviewID) review, _, err := client.PullRequests.GetPullReview(ctx, owner, repo, index, reviewID)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get review %v for %v/%v/pr/%v err: %v", reviewID, owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("get review %v for %v/%v/pr/%v err: %v", reviewID, owner, repo, index, err))
} }
@@ -595,7 +554,6 @@ func getPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.
} }
func listPullRequestReviewCommentsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func listPullRequestReviewCommentsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called listPullRequestReviewCommentsFn")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -619,7 +577,7 @@ func listPullRequestReviewCommentsFn(ctx context.Context, req mcp.CallToolReques
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.ListPullReviewComments(owner, repo, index, reviewID) comments, _, err := client.PullRequests.ListPullReviewComments(ctx, owner, repo, index, reviewID)
if err != nil { 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)) return to.ErrorResult(fmt.Errorf("list review comments for review %v on %v/%v/pr/%v err: %v", reviewID, owner, repo, index, err))
} }
@@ -628,7 +586,6 @@ func listPullRequestReviewCommentsFn(ctx context.Context, req mcp.CallToolReques
} }
func createPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func createPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called createPullRequestReviewFn")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -684,7 +641,7 @@ func createPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*m
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
} }
review, _, err := client.CreatePullReview(owner, repo, index, opt) review, _, err := client.PullRequests.CreatePullReview(ctx, owner, repo, index, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("create review for %v/%v/pr/%v err: %v", owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("create review for %v/%v/pr/%v err: %v", owner, repo, index, err))
} }
@@ -693,7 +650,6 @@ func createPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*m
} }
func submitPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func submitPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called submitPullRequestReviewFn")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -728,7 +684,7 @@ func submitPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*m
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
} }
review, _, err := client.SubmitPullReview(owner, repo, index, reviewID, opt) review, _, err := client.PullRequests.SubmitPullReview(ctx, owner, repo, index, reviewID, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("submit review %v for %v/%v/pr/%v err: %v", reviewID, owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("submit review %v for %v/%v/pr/%v err: %v", reviewID, owner, repo, index, err))
} }
@@ -737,7 +693,6 @@ func submitPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*m
} }
func deletePullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func deletePullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called deletePullRequestReviewFn")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -761,7 +716,7 @@ func deletePullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*m
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
} }
_, err = client.DeletePullReview(owner, repo, index, reviewID) _, err = client.PullRequests.DeletePullReview(ctx, owner, repo, index, reviewID)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("delete review %v for %v/%v/pr/%v err: %v", reviewID, owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("delete review %v for %v/%v/pr/%v err: %v", reviewID, owner, repo, index, err))
} }
@@ -777,7 +732,6 @@ func deletePullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*m
} }
func dismissPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func dismissPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called dismissPullRequestReviewFn")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -806,7 +760,7 @@ func dismissPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
} }
_, err = client.DismissPullReview(owner, repo, index, reviewID, opt) _, err = client.PullRequests.DismissPullReview(ctx, owner, repo, index, reviewID, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("dismiss review %v for %v/%v/pr/%v err: %v", reviewID, owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("dismiss review %v for %v/%v/pr/%v err: %v", reviewID, owner, repo, index, err))
} }
@@ -822,7 +776,6 @@ func dismissPullRequestReviewFn(ctx context.Context, req mcp.CallToolRequest) (*
} }
func mergePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func mergePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called mergePullRequestFn")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -850,18 +803,19 @@ func mergePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
forceMerge, _ := args["force_merge"].(bool) forceMerge, _ := args["force_merge"].(bool)
mergeWhenChecksSucceed, _ := args["merge_when_checks_succeed"].(bool) mergeWhenChecksSucceed, _ := args["merge_when_checks_succeed"].(bool)
headCommitID, _ := args["head_commit_id"].(string) headCommitID, _ := args["head_commit_id"].(string)
deleteBranchAfterMerge := &deleteBranch
opt := gitea_sdk.MergePullRequestOption{ opt := gitea_sdk.MergePullRequestOption{
Style: gitea_sdk.MergeStyle(mergeStyle), Style: gitea_sdk.MergeStyle(mergeStyle),
Title: title, Title: title,
Message: message, Message: message,
DeleteBranchAfterMerge: deleteBranch, DeleteBranchAfterMerge: deleteBranchAfterMerge,
ForceMerge: forceMerge, ForceMerge: forceMerge,
MergeWhenChecksSucceed: mergeWhenChecksSucceed, MergeWhenChecksSucceed: mergeWhenChecksSucceed,
HeadCommitId: headCommitID, HeadCommitId: headCommitID,
} }
merged, resp, err := client.MergePullRequest(owner, repo, index, opt) merged, resp, err := client.PullRequests.MergePullRequest(ctx, owner, repo, index, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("merge %v/%v/pr/%v err: %v", owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("merge %v/%v/pr/%v err: %v", owner, repo, index, err))
} }
@@ -886,7 +840,6 @@ func mergePullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
} }
func editPullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func editPullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called editPullRequestFn")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -914,7 +867,7 @@ func editPullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
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))
} }
pr, _, err := client.GetPullRequest(owner, repo, index) pr, _, err := client.PullRequests.GetPullRequest(ctx, owner, repo, index)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get %v/%v/pr/%v err: %v", owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("get %v/%v/pr/%v err: %v", owner, repo, index, err))
} }
@@ -922,9 +875,10 @@ func editPullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
} }
opt.Title = applyDraftPrefix(opt.Title, draft) opt.Title = applyDraftPrefix(opt.Title, draft)
} }
if body, ok := args["body"].(string); ok { opt.Body = params.GetPresentStringPtr(args, "body")
opt.Body = new(body) opt.AllowMaintainerEdit = params.GetOptionalBoolPtr(args, "allow_maintainer_edit")
} opt.RemoveDeadline = params.GetOptionalBoolPtr(args, "remove_deadline")
opt.Deadline = params.GetOptionalTime(args, "deadline")
if base, ok := args["base"].(string); ok { if base, ok := args["base"].(string); ok {
opt.Base = base opt.Base = base
} }
@@ -940,25 +894,19 @@ func editPullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
} }
} }
if state, ok := args["state"].(string); ok { if state, ok := args["state"].(string); ok {
opt.State = new(gitea_sdk.StateType(state)) s := gitea_sdk.StateType(state)
} opt.State = &s
if allowMaintainerEdit, ok := args["allow_maintainer_edit"].(bool); ok {
opt.AllowMaintainerEdit = new(allowMaintainerEdit)
} }
if labelIDs, err := params.GetInt64Slice(args, "labels"); err == nil { if labelIDs, err := params.GetInt64Slice(args, "labels"); err == nil {
opt.Labels = labelIDs opt.Labels = labelIDs
} }
opt.Deadline = params.GetOptionalTime(args, "deadline")
if removeDeadline, ok := args["remove_deadline"].(bool); ok {
opt.RemoveDeadline = &removeDeadline
}
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))
} }
pr, _, err := client.EditPullRequest(owner, repo, index, opt) pr, _, err := client.PullRequests.EditPullRequest(ctx, owner, repo, index, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("edit %v/%v/pr/%v err: %v", owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("edit %v/%v/pr/%v err: %v", owner, repo, index, err))
} }
@@ -967,7 +915,6 @@ func editPullRequestFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
} }
func updatePullRequestBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func updatePullRequestBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called updatePullRequestBranchFn")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -990,7 +937,6 @@ func updatePullRequestBranchFn(ctx context.Context, req mcp.CallToolRequest) (*m
} }
func getPullRequestFilesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func getPullRequestFilesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called getPullRequestFilesFn")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -1009,7 +955,7 @@ func getPullRequestFilesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.C
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))
} }
files, _, err := client.ListPullRequestFiles(owner, repo, index, gitea_sdk.ListPullRequestFilesOptions{ files, _, err := client.PullRequests.ListPullRequestFiles(ctx, owner, repo, index, gitea_sdk.ListPullRequestFilesOptions{
ListOptions: gitea_sdk.ListOptions{Page: page, PageSize: pageSize}, ListOptions: gitea_sdk.ListOptions{Page: page, PageSize: pageSize},
}) })
if err != nil { if err != nil {
@@ -1019,7 +965,6 @@ func getPullRequestFilesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.C
} }
func getPullRequestStatusFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func getPullRequestStatusFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called getPullRequestStatusFn")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -1037,7 +982,7 @@ func getPullRequestStatusFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.
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))
} }
pr, _, err := client.GetPullRequest(owner, repo, index) pr, _, err := client.PullRequests.GetPullRequest(ctx, owner, repo, index)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get %v/%v/pr/%v err: %v", owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("get %v/%v/pr/%v err: %v", owner, repo, index, err))
} }
@@ -1045,7 +990,7 @@ func getPullRequestStatusFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.
return to.ErrorResult(fmt.Errorf("pr %v/%v/%v has no head SHA", owner, repo, index)) return to.ErrorResult(fmt.Errorf("pr %v/%v/%v has no head SHA", owner, repo, index))
} }
status, _, err := client.GetCombinedStatus(owner, repo, pr.Head.Sha) status, _, err := client.Repositories.GetCombinedStatus(ctx, owner, repo, pr.Head.Sha)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get %v/%v/pr/%v status err: %v", owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("get %v/%v/pr/%v status err: %v", owner, repo, index, err))
} }
+3
View File
@@ -158,6 +158,9 @@ func Test_mergePullRequestFn(t *testing.T) {
case "/api/v1/version": case "/api/v1/version":
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"version":"1.12.0"}`)) _, _ = w.Write([]byte(`{"version":"1.12.0"}`))
case fmt.Sprintf("/api/v1/repos/%s/%s/pulls/%d", owner, repo, index):
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"head":{"sha":"abc123"}}`))
case fmt.Sprintf("/api/v1/repos/%s/%s", owner, repo): case fmt.Sprintf("/api/v1/repos/%s/%s", owner, repo):
w.Header().Set("Content-Type", "application/json") w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"private":false}`)) _, _ = w.Write([]byte(`{"private":false}`))
+10 -62
View File
@@ -1,63 +1,11 @@
package pull package pull
import ( import (
"fmt" "gitea.com/gitea/gitea-mcp/pkg/slim"
"strings"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "gitea.dev/sdk"
) )
func bodyWithAttachments(body string, atts []*gitea_sdk.Attachment) string {
links := make([]string, 0, len(atts))
for _, a := range atts {
if a == nil || a.DownloadURL == "" {
continue
}
links = append(links, fmt.Sprintf("[%s](%s)", a.Name, a.DownloadURL))
}
if len(links) == 0 {
return body
}
joined := strings.Join(links, "\n")
if body == "" {
return joined
}
return body + "\n\n" + joined
}
func userLogin(u *gitea_sdk.User) string {
if u == nil {
return ""
}
return u.UserName
}
func userLogins(users []*gitea_sdk.User) []string {
if len(users) == 0 {
return nil
}
out := make([]string, 0, len(users))
for _, u := range users {
if u != nil {
out = append(out, u.UserName)
}
}
return out
}
func labelNames(labels []*gitea_sdk.Label) []string {
if len(labels) == 0 {
return nil
}
out := make([]string, 0, len(labels))
for _, l := range labels {
if l != nil {
out = append(out, l.Name)
}
}
return out
}
func repoRef(r *gitea_sdk.Repository) map[string]any { func repoRef(r *gitea_sdk.Repository) map[string]any {
if r == nil { if r == nil {
return nil return nil
@@ -81,8 +29,8 @@ func slimPullRequest(pr *gitea_sdk.PullRequest) map[string]any {
"merged": pr.HasMerged, "merged": pr.HasMerged,
"mergeable": pr.Mergeable, "mergeable": pr.Mergeable,
"html_url": pr.HTMLURL, "html_url": pr.HTMLURL,
"user": userLogin(pr.Poster), "user": slim.UserLogin(pr.Poster),
"labels": labelNames(pr.Labels), "labels": slim.LabelNames(pr.Labels),
"comments": pr.Comments, "comments": pr.Comments,
"created_at": pr.Created, "created_at": pr.Created,
"updated_at": pr.Updated, "updated_at": pr.Updated,
@@ -91,7 +39,7 @@ func slimPullRequest(pr *gitea_sdk.PullRequest) map[string]any {
if pr.HasMerged { if pr.HasMerged {
m["merged_at"] = pr.Merged m["merged_at"] = pr.Merged
m["merge_commit_sha"] = pr.MergedCommitID m["merge_commit_sha"] = pr.MergedCommitID
m["merged_by"] = userLogin(pr.MergedBy) m["merged_by"] = slim.UserLogin(pr.MergedBy)
} }
if pr.Head != nil { if pr.Head != nil {
head := map[string]any{"ref": pr.Head.Ref, "sha": pr.Head.Sha} head := map[string]any{"ref": pr.Head.Ref, "sha": pr.Head.Sha}
@@ -117,7 +65,7 @@ func slimPullRequest(pr *gitea_sdk.PullRequest) map[string]any {
m["changed_files"] = *pr.ChangedFiles m["changed_files"] = *pr.ChangedFiles
} }
if len(pr.Assignees) > 0 { if len(pr.Assignees) > 0 {
m["assignees"] = userLogins(pr.Assignees) m["assignees"] = slim.UserLogins(pr.Assignees)
} }
if pr.Milestone != nil { if pr.Milestone != nil {
m["milestone"] = pr.Milestone.Title m["milestone"] = pr.Milestone.Title
@@ -141,7 +89,7 @@ func slimPullRequests(prs []*gitea_sdk.PullRequest) []map[string]any {
"draft": pr.Draft, "draft": pr.Draft,
"merged": pr.HasMerged, "merged": pr.HasMerged,
"html_url": pr.HTMLURL, "html_url": pr.HTMLURL,
"user": userLogin(pr.Poster), "user": slim.UserLogin(pr.Poster),
"created_at": pr.Created, "created_at": pr.Created,
"updated_at": pr.Updated, "updated_at": pr.Updated,
} }
@@ -152,7 +100,7 @@ func slimPullRequests(prs []*gitea_sdk.PullRequest) []map[string]any {
m["base"] = pr.Base.Ref m["base"] = pr.Base.Ref
} }
if len(pr.Labels) > 0 { if len(pr.Labels) > 0 {
m["labels"] = labelNames(pr.Labels) m["labels"] = slim.LabelNames(pr.Labels)
} }
out = append(out, m) out = append(out, m)
} }
@@ -167,7 +115,7 @@ func slimReview(r *gitea_sdk.PullReview) map[string]any {
"id": r.ID, "id": r.ID,
"state": r.State, "state": r.State,
"body": r.Body, "body": r.Body,
"user": userLogin(r.Reviewer), "user": slim.UserLogin(r.Reviewer),
"comments_count": r.CodeCommentsCount, "comments_count": r.CodeCommentsCount,
"submitted_at": r.Submitted, "submitted_at": r.Submitted,
"html_url": r.HTMLURL, "html_url": r.HTMLURL,
@@ -196,7 +144,7 @@ func slimReviewComment(c *gitea_sdk.PullReviewComment) map[string]any {
"position": c.LineNum, "position": c.LineNum,
"old_position": c.OldLineNum, "old_position": c.OldLineNum,
"diff_hunk": c.DiffHunk, "diff_hunk": c.DiffHunk,
"user": userLogin(c.Reviewer), "user": slim.UserLogin(c.Reviewer),
"html_url": c.HTMLURL, "html_url": c.HTMLURL,
"created_at": c.Created, "created_at": c.Created,
"updated_at": c.Updated, "updated_at": c.Updated,
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"testing" "testing"
"time" "time"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "gitea.dev/sdk"
) )
func TestSlimPullRequest(t *testing.T) { func TestSlimPullRequest(t *testing.T) {
+8 -9
View File
@@ -6,11 +6,10 @@ import (
"gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/annotation"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "gitea.com/gitea/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "gitea.dev/sdk"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"
) )
@@ -24,6 +23,7 @@ const (
var ( var (
CreateBranchTool = mcp.NewTool( CreateBranchTool = mcp.NewTool(
CreateBranchToolName, CreateBranchToolName,
mcp.WithDescription("Create a new branch in a repository, optionally from a specific source branch (defaults to the repository's default branch)."),
mcp.WithToolAnnotation(annotation.Write("Create a new branch")), mcp.WithToolAnnotation(annotation.Write("Create a new branch")),
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)),
@@ -33,6 +33,7 @@ var (
DeleteBranchTool = mcp.NewTool( DeleteBranchTool = mcp.NewTool(
DeleteBranchToolName, DeleteBranchToolName,
mcp.WithDescription("Permanently delete a branch from a repository. This action is destructive and cannot be undone."),
mcp.WithToolAnnotation(annotation.Destructive("Delete a branch")), mcp.WithToolAnnotation(annotation.Destructive("Delete a branch")),
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)),
@@ -41,6 +42,7 @@ var (
ListBranchesTool = mcp.NewTool( ListBranchesTool = mcp.NewTool(
ListBranchesToolName, ListBranchesToolName,
mcp.WithDescription("List all branches in a repository, paginated."),
mcp.WithToolAnnotation(annotation.ReadOnly("List repository branches")), mcp.WithToolAnnotation(annotation.ReadOnly("List repository branches")),
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)),
@@ -65,7 +67,6 @@ 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")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -85,7 +86,7 @@ func CreateBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
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))
} }
_, _, err = client.CreateBranch(owner, repo, gitea_sdk.CreateBranchOption{ _, _, err = client.Repositories.CreateBranch(ctx, owner, repo, gitea_sdk.CreateBranchOption{
BranchName: branch, BranchName: branch,
OldBranchName: oldBranch, OldBranchName: oldBranch,
}) })
@@ -93,11 +94,10 @@ func CreateBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
return to.ErrorResult(fmt.Errorf("create branch error: %v", err)) return to.ErrorResult(fmt.Errorf("create branch error: %v", err))
} }
return mcp.NewToolResultText("Branch Created"), nil return to.TextResult("Branch Created")
} }
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")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -115,7 +115,7 @@ func DeleteBranchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
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))
} }
_, _, err = client.DeleteRepoBranch(owner, repo, branch) _, _, err = client.Repositories.DeleteRepoBranch(ctx, owner, repo, branch)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("delete branch error: %v", err)) return to.ErrorResult(fmt.Errorf("delete branch error: %v", err))
} }
@@ -124,7 +124,6 @@ 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")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -145,7 +144,7 @@ func ListBranchesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
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))
} }
branches, _, err := client.ListRepoBranches(owner, repo, opt) branches, _, err := client.Repositories.ListRepoBranches(ctx, owner, repo, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("list branches error: %v", err)) return to.ErrorResult(fmt.Errorf("list branches error: %v", err))
} }
+5 -6
View File
@@ -6,11 +6,10 @@ import (
"gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/annotation"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "gitea.com/gitea/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "gitea.dev/sdk"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"
) )
@@ -23,6 +22,7 @@ const (
var ( var (
ListRepoCommitsTool = mcp.NewTool( ListRepoCommitsTool = mcp.NewTool(
ListRepoCommitsToolName, ListRepoCommitsToolName,
mcp.WithDescription("List commits in a repository, optionally starting from a specific branch or SHA and filtered to commits touching a given file path."),
mcp.WithToolAnnotation(annotation.ReadOnly("List repository commits")), mcp.WithToolAnnotation(annotation.ReadOnly("List repository commits")),
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)),
@@ -34,6 +34,7 @@ var (
GetCommitTool = mcp.NewTool( GetCommitTool = mcp.NewTool(
GetCommitToolName, GetCommitToolName,
mcp.WithDescription("Get details for a single commit in a repository by its SHA."),
mcp.WithToolAnnotation(annotation.ReadOnly("Get commit details")), mcp.WithToolAnnotation(annotation.ReadOnly("Get commit details")),
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)),
@@ -53,7 +54,6 @@ 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")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -78,7 +78,7 @@ func ListRepoCommitsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
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))
} }
commits, _, err := client.ListRepoCommits(owner, repo, opt) commits, _, err := client.Repositories.ListRepoCommits(ctx, owner, repo, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("list repo commits err: %v", err)) return to.ErrorResult(fmt.Errorf("list repo commits err: %v", err))
} }
@@ -86,7 +86,6 @@ func ListRepoCommitsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
} }
func GetCommitFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetCommitFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetCommitFn")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -104,7 +103,7 @@ func GetCommitFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRes
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))
} }
commit, _, err := client.GetSingleCommit(owner, repo, sha) commit, _, err := client.Repositories.GetSingleCommit(ctx, owner, repo, sha)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get commit %v err: %v", sha, err)) return to.ErrorResult(fmt.Errorf("get commit %v err: %v", sha, err))
} }
+8 -11
View File
@@ -10,11 +10,10 @@ import (
"gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/annotation"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "gitea.com/gitea/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "gitea.dev/sdk"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"
) )
@@ -40,6 +39,7 @@ var (
GetDirContentTool = mcp.NewTool( GetDirContentTool = mcp.NewTool(
GetDirToolName, GetDirToolName,
mcp.WithDescription("List the entries (files and subdirectories) in a repository directory at a given ref (branch, tag, or commit SHA)."),
mcp.WithToolAnnotation(annotation.ReadOnly("Get directory contents")), mcp.WithToolAnnotation(annotation.ReadOnly("Get directory contents")),
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)),
@@ -63,6 +63,7 @@ var (
DeleteFileTool = mcp.NewTool( DeleteFileTool = mcp.NewTool(
DeleteFileToolName, DeleteFileToolName,
mcp.WithDescription("Delete a file from a repository by committing the removal to a branch. Requires the file's current SHA and a commit message."),
mcp.WithToolAnnotation(annotation.Destructive("Delete a file")), mcp.WithToolAnnotation(annotation.Destructive("Delete a file")),
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)),
@@ -98,7 +99,6 @@ type ContentLine struct {
} }
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")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -117,7 +117,7 @@ func GetFileContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
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))
} }
content, _, err := client.GetContents(owner, repo, ref, filePath) content, _, err := client.Repositories.GetContents(ctx, owner, repo, ref, filePath)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get file err: %v", err)) return to.ErrorResult(fmt.Errorf("get file err: %v", err))
} }
@@ -162,7 +162,6 @@ func GetFileContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
} }
func GetDirContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetDirContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetDirContentFn")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -181,7 +180,7 @@ func GetDirContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
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))
} }
content, _, err := client.ListContents(owner, repo, ref, filePath) content, _, err := client.Repositories.ListContents(ctx, owner, repo, ref, filePath)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get dir content err: %v", err)) return to.ErrorResult(fmt.Errorf("get dir content err: %v", err))
} }
@@ -189,7 +188,6 @@ func GetDirContentFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
} }
func CreateOrUpdateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func CreateOrUpdateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called CreateOrUpdateFileFn")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -223,7 +221,7 @@ func CreateOrUpdateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca
BranchName: branchName, BranchName: branchName,
}, },
} }
_, _, err = client.UpdateFile(owner, repo, filePath, opt) _, _, err = client.Repositories.UpdateFile(ctx, owner, repo, filePath, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("update file err: %v", err)) return to.ErrorResult(fmt.Errorf("update file err: %v", err))
} }
@@ -241,7 +239,7 @@ func CreateOrUpdateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca
if newBranch, ok := args["new_branch_name"].(string); ok && newBranch != "" { if newBranch, ok := args["new_branch_name"].(string); ok && newBranch != "" {
opt.NewBranchName = newBranch opt.NewBranchName = newBranch
} }
_, _, err = client.CreateFile(owner, repo, filePath, opt) _, _, err = client.Repositories.CreateFile(ctx, owner, repo, filePath, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("create file err: %v", err)) return to.ErrorResult(fmt.Errorf("create file err: %v", err))
} }
@@ -249,7 +247,6 @@ func CreateOrUpdateFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Ca
} }
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")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -280,7 +277,7 @@ func DeleteFileFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
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))
} }
_, err = client.DeleteFile(owner, repo, filePath, opt) _, err = client.Repositories.DeleteFile(ctx, owner, repo, filePath, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("delete file err: %v", err)) return to.ErrorResult(fmt.Errorf("delete file err: %v", err))
} }
+21 -34
View File
@@ -6,11 +6,10 @@ import (
"gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/annotation"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "gitea.com/gitea/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "gitea.dev/sdk"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"
) )
@@ -26,6 +25,7 @@ const (
var ( var (
CreateReleaseTool = mcp.NewTool( CreateReleaseTool = mcp.NewTool(
CreateReleaseToolName, CreateReleaseToolName,
mcp.WithDescription("Create a new release in a repository from a tag, optionally marking it as a draft or pre-release."),
mcp.WithToolAnnotation(annotation.Write("Create a release")), mcp.WithToolAnnotation(annotation.Write("Create a release")),
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)),
@@ -39,6 +39,7 @@ var (
DeleteReleaseTool = mcp.NewTool( DeleteReleaseTool = mcp.NewTool(
DeleteReleaseToolName, DeleteReleaseToolName,
mcp.WithDescription("Delete a release from a repository by its numeric ID. This action is destructive and cannot be undone."),
mcp.WithToolAnnotation(annotation.Destructive("Delete a release")), mcp.WithToolAnnotation(annotation.Destructive("Delete a release")),
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)),
@@ -56,6 +57,7 @@ var (
GetLatestReleaseTool = mcp.NewTool( GetLatestReleaseTool = mcp.NewTool(
GetLatestReleaseToolName, GetLatestReleaseToolName,
mcp.WithDescription("Get the most recent published (non-draft) release in a repository."),
mcp.WithToolAnnotation(annotation.ReadOnly("Get latest release")), mcp.WithToolAnnotation(annotation.ReadOnly("Get latest release")),
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)),
@@ -63,6 +65,7 @@ var (
ListReleasesTool = mcp.NewTool( ListReleasesTool = mcp.NewTool(
ListReleasesToolName, ListReleasesToolName,
mcp.WithDescription("List releases in a repository, optionally filtered to drafts or pre-releases."),
mcp.WithToolAnnotation(annotation.ReadOnly("List releases")), mcp.WithToolAnnotation(annotation.ReadOnly("List releases")),
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)),
@@ -97,7 +100,6 @@ func init() {
} }
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")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -127,7 +129,7 @@ func CreateReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
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))
} }
_, _, err = client.CreateRelease(owner, repo, gitea_sdk.CreateReleaseOption{ _, _, err = client.Releases.CreateRelease(ctx, owner, repo, gitea_sdk.CreateReleaseOption{
TagName: tagName, TagName: tagName,
Target: target, Target: target,
Title: title, Title: title,
@@ -136,14 +138,13 @@ func CreateReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
IsPrerelease: isPreRelease, IsPrerelease: isPreRelease,
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("create release error: %v", err) return to.ErrorResult(fmt.Errorf("create release error: %v", err))
} }
return mcp.NewToolResultText("Release Created"), nil return to.TextResult("Release Created")
} }
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")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -162,16 +163,15 @@ func DeleteReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
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))
} }
_, err = client.DeleteRelease(owner, repo, id) _, err = client.Releases.DeleteRelease(ctx, owner, repo, id)
if err != nil { if err != nil {
return nil, fmt.Errorf("delete release error: %v", err) return to.ErrorResult(fmt.Errorf("delete release error: %v", err))
} }
return to.TextResult("Release deleted successfully") return to.TextResult("Release deleted successfully")
} }
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")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -190,16 +190,15 @@ func GetReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
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))
} }
release, _, err := client.GetRelease(owner, repo, id) release, _, err := client.Releases.GetRelease(ctx, owner, repo, id)
if err != nil { if err != nil {
return nil, fmt.Errorf("get release error: %v", err) return to.ErrorResult(fmt.Errorf("get release error: %v", err))
} }
return to.TextResult(slimRelease(release)) return to.TextResult(slimRelease(release))
} }
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")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -214,16 +213,15 @@ func GetLatestReleaseFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
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))
} }
release, _, err := client.GetLatestRelease(owner, repo) release, _, err := client.Releases.GetLatestRelease(ctx, owner, repo)
if err != nil { if err != nil {
return nil, fmt.Errorf("get latest release error: %v", err) return to.ErrorResult(fmt.Errorf("get latest release error: %v", err))
} }
return to.TextResult(slimRelease(release)) return to.TextResult(slimRelease(release))
} }
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")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -233,33 +231,22 @@ func ListReleasesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
var pIsDraft *bool page, pageSize := params.GetPagination(args, 20)
isDraft, ok := args["is_draft"].(bool)
if ok {
pIsDraft = new(isDraft)
}
var pIsPreRelease *bool
isPreRelease, ok := args["is_pre_release"].(bool)
if ok {
pIsPreRelease = new(isPreRelease)
}
page := params.GetOptionalInt(args, "page", 1)
pageSize := params.GetOptionalInt(args, "per_page", 20)
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))
} }
releases, _, err := client.ListReleases(owner, repo, gitea_sdk.ListReleasesOptions{ releases, _, err := client.Releases.ListReleases(ctx, owner, repo, gitea_sdk.ListReleasesOptions{
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{
Page: int(page), Page: page,
PageSize: int(pageSize), PageSize: pageSize,
}, },
IsDraft: pIsDraft, IsDraft: params.GetOptionalBoolPtr(args, "is_draft"),
IsPreRelease: pIsPreRelease, IsPreRelease: params.GetOptionalBoolPtr(args, "is_pre_release"),
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("list releases error: %v", err) return to.ErrorResult(fmt.Errorf("list releases error: %v", err))
} }
return to.TextResult(slimReleases(releases)) return to.TextResult(slimReleases(releases))
+18 -29
View File
@@ -2,17 +2,16 @@ package repo
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/annotation"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "gitea.com/gitea/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/slim"
"gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/to"
"gitea.com/gitea/gitea-mcp/pkg/tool" "gitea.com/gitea/gitea-mcp/pkg/tool"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "gitea.dev/sdk"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"
) )
@@ -29,6 +28,7 @@ const (
var ( var (
CreateRepoTool = mcp.NewTool( CreateRepoTool = mcp.NewTool(
CreateRepoToolName, CreateRepoToolName,
mcp.WithDescription("Create a new Git repository, optionally under an organization (defaults to the authenticated user's account), with options for visibility, template, license, .gitignore, and initial README."),
mcp.WithToolAnnotation(annotation.Write("Create a new repository")), mcp.WithToolAnnotation(annotation.Write("Create a new repository")),
mcp.WithString("name", mcp.Required()), mcp.WithString("name", mcp.Required()),
mcp.WithString("description"), mcp.WithString("description"),
@@ -47,6 +47,7 @@ var (
ForkRepoTool = mcp.NewTool( ForkRepoTool = mcp.NewTool(
ForkRepoToolName, ForkRepoToolName,
mcp.WithDescription("Fork an existing repository into the authenticated user's account or a target organization, optionally under a new name."),
mcp.WithToolAnnotation(annotation.Write("Fork a repository")), mcp.WithToolAnnotation(annotation.Write("Fork a repository")),
mcp.WithString("user", mcp.Required(), mcp.Description("owner of source repo")), mcp.WithString("user", mcp.Required(), mcp.Description("owner of source repo")),
mcp.WithString("repo", mcp.Required()), mcp.WithString("repo", mcp.Required()),
@@ -56,6 +57,7 @@ var (
ListMyReposTool = mcp.NewTool( ListMyReposTool = mcp.NewTool(
ListMyReposToolName, ListMyReposToolName,
mcp.WithDescription("List repositories owned by the authenticated user."),
mcp.WithToolAnnotation(annotation.ReadOnly("List my repositories")), mcp.WithToolAnnotation(annotation.ReadOnly("List my repositories")),
mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1), mcp.Min(1)), mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1), mcp.Min(1)),
mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30), mcp.Min(1)), mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30), mcp.Min(1)),
@@ -63,6 +65,7 @@ var (
ListOrgReposTool = mcp.NewTool( ListOrgReposTool = mcp.NewTool(
ListOrgReposToolName, ListOrgReposToolName,
mcp.WithDescription("List repositories belonging to an organization."),
mcp.WithToolAnnotation(annotation.ReadOnly("List organization repositories")), mcp.WithToolAnnotation(annotation.ReadOnly("List organization repositories")),
mcp.WithString("org", mcp.Required()), mcp.WithString("org", mcp.Required()),
mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1), mcp.Min(1)), mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1), mcp.Min(1)),
@@ -90,7 +93,6 @@ func init() {
} }
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")
args := req.GetArguments() args := req.GetArguments()
name, err := params.GetString(args, "name") name, err := params.GetString(args, "name")
if err != nil { if err != nil {
@@ -130,21 +132,20 @@ func CreateRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRe
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
} }
if organization != "" { if organization != "" {
repo, _, err = client.CreateOrgRepo(organization, opt) repo, _, err = client.Repositories.CreateOrgRepo(ctx, organization, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("create organization repository '%s' in '%s' err: %v", name, organization, err)) return to.ErrorResult(fmt.Errorf("create organization repository '%s' in '%s' err: %v", name, organization, err))
} }
} else { } else {
repo, _, err = client.CreateRepo(opt) repo, _, err = client.Repositories.CreateRepo(ctx, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("create repository '%s' err: %v", name, err)) return to.ErrorResult(fmt.Errorf("create repository '%s' err: %v", name, err))
} }
} }
return to.TextResult(slimRepo(repo)) return to.TextResult(slim.Repo(repo))
} }
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")
args := req.GetArguments() args := req.GetArguments()
user, err := params.GetString(args, "user") user, err := params.GetString(args, "user")
if err != nil { if err != nil {
@@ -154,25 +155,15 @@ func ForkRepoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResu
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
organization, ok := args["organization"].(string)
organizationPtr := new(organization)
if !ok || organization == "" {
organizationPtr = nil
}
name, ok := args["name"].(string)
namePtr := new(name)
if !ok || name == "" {
namePtr = nil
}
opt := gitea_sdk.CreateForkOption{ opt := gitea_sdk.CreateForkOption{
Organization: organizationPtr, Organization: params.GetOptionalStringPtr(args, "organization"),
Name: namePtr, Name: params.GetOptionalStringPtr(args, "name"),
} }
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))
} }
_, _, err = client.CreateFork(user, repo, opt) _, _, err = client.Repositories.CreateFork(ctx, user, repo, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("fork repository error: %v", err)) return to.ErrorResult(fmt.Errorf("fork repository error: %v", err))
} }
@@ -180,7 +171,6 @@ 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")
page, pageSize := params.GetPagination(req.GetArguments(), 30) page, pageSize := params.GetPagination(req.GetArguments(), 30)
opt := gitea_sdk.ListReposOptions{ opt := gitea_sdk.ListReposOptions{
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{
@@ -192,19 +182,18 @@ func ListMyReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
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))
} }
repos, _, err := client.ListMyRepos(opt) repos, _, err := client.Repositories.ListMyRepos(ctx, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("list my repositories error: %v", err)) return to.ErrorResult(fmt.Errorf("list my repositories error: %v", err))
} }
return to.TextResult(slimRepos(repos)) return to.TextResult(slim.Repos(repos))
} }
func ListOrgReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ListOrgReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ListOrgReposFn") org, err := params.GetString(req.GetArguments(), "org")
org, ok := req.GetArguments()["org"].(string) if err != nil {
if !ok { return to.ErrorResult(err)
return to.ErrorResult(errors.New("organization name is required"))
} }
page, pageSize := params.GetPagination(req.GetArguments(), 100) page, pageSize := params.GetPagination(req.GetArguments(), 100)
opt := gitea_sdk.ListOrgReposOptions{ opt := gitea_sdk.ListOrgReposOptions{
@@ -217,7 +206,7 @@ func ListOrgReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTool
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))
} }
repos, _, err := client.ListOrgRepos(org, opt) repos, _, err := client.Repositories.ListOrgRepos(ctx, org, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("list organization '%s' repositories error: %v", org, err)) return to.ErrorResult(fmt.Errorf("list organization '%s' repositories error: %v", org, err))
} }
+4 -49
View File
@@ -1,56 +1,11 @@
package repo package repo
import ( import (
gitea_sdk "code.gitea.io/sdk/gitea" "gitea.com/gitea/gitea-mcp/pkg/slim"
gitea_sdk "gitea.dev/sdk"
) )
func userLogin(u *gitea_sdk.User) string {
if u == nil {
return ""
}
return u.UserName
}
func slimRepo(r *gitea_sdk.Repository) map[string]any {
if r == nil {
return nil
}
m := map[string]any{
"id": r.ID,
"full_name": r.FullName,
"description": r.Description,
"html_url": r.HTMLURL,
"clone_url": r.CloneURL,
"ssh_url": r.SSHURL,
"default_branch": r.DefaultBranch,
"private": r.Private,
"fork": r.Fork,
"archived": r.Archived,
"language": r.Language,
"stars_count": r.Stars,
"forks_count": r.Forks,
"open_issues_count": r.OpenIssues,
"open_pr_counter": r.OpenPulls,
"created_at": r.Created,
"updated_at": r.Updated,
}
if r.Owner != nil {
m["owner"] = r.Owner.UserName
}
if len(r.Topics) > 0 {
m["topics"] = r.Topics
}
return m
}
func slimRepos(repos []*gitea_sdk.Repository) []map[string]any {
out := make([]map[string]any, 0, len(repos))
for _, r := range repos {
out = append(out, slimRepo(r))
}
return out
}
func slimBranch(b *gitea_sdk.Branch) map[string]any { func slimBranch(b *gitea_sdk.Branch) map[string]any {
if b == nil { if b == nil {
return nil return nil
@@ -144,7 +99,7 @@ func slimRelease(r *gitea_sdk.Release) map[string]any {
"draft": r.IsDraft, "draft": r.IsDraft,
"prerelease": r.IsPrerelease, "prerelease": r.IsPrerelease,
"html_url": r.HTMLURL, "html_url": r.HTMLURL,
"author": userLogin(r.Publisher), "author": slim.UserLogin(r.Publisher),
"created_at": r.CreatedAt, "created_at": r.CreatedAt,
"published_at": r.PublishedAt, "published_at": r.PublishedAt,
} }
+1 -34
View File
@@ -3,42 +3,9 @@ package repo
import ( import (
"testing" "testing"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "gitea.dev/sdk"
) )
func TestSlimRepo(t *testing.T) {
r := &gitea_sdk.Repository{
ID: 1,
FullName: "org/repo",
Description: "A test repo",
HTMLURL: "https://gitea.com/org/repo",
CloneURL: "https://gitea.com/org/repo.git",
SSHURL: "git@gitea.com:org/repo.git",
DefaultBranch: "main",
Private: false,
Fork: false,
Archived: false,
Language: "Go",
Stars: 10,
Forks: 2,
Owner: &gitea_sdk.User{UserName: "org"},
Topics: []string{"mcp", "gitea"},
}
m := slimRepo(r)
if m["full_name"] != "org/repo" {
t.Errorf("expected full_name org/repo, got %v", m["full_name"])
}
if m["owner"] != "org" {
t.Errorf("expected owner org, got %v", m["owner"])
}
topics := m["topics"].([]string)
if len(topics) != 2 {
t.Errorf("expected 2 topics, got %d", len(topics))
}
}
func TestSlimTag(t *testing.T) { func TestSlimTag(t *testing.T) {
tag := &gitea_sdk.Tag{ tag := &gitea_sdk.Tag{
Name: "v1.0.0", Name: "v1.0.0",
+14 -15
View File
@@ -6,11 +6,10 @@ import (
"gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/annotation"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "gitea.com/gitea/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "gitea.dev/sdk"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"
) )
@@ -25,6 +24,7 @@ const (
var ( var (
CreateTagTool = mcp.NewTool( CreateTagTool = mcp.NewTool(
CreateTagToolName, CreateTagToolName,
mcp.WithDescription("Create a new Git tag in a repository at a target commit, branch, or existing tag, with an optional annotation message."),
mcp.WithToolAnnotation(annotation.Write("Create a tag")), mcp.WithToolAnnotation(annotation.Write("Create a tag")),
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)),
@@ -35,6 +35,7 @@ var (
DeleteTagTool = mcp.NewTool( DeleteTagTool = mcp.NewTool(
DeleteTagToolName, DeleteTagToolName,
mcp.WithDescription("Permanently delete a tag from a repository. This action is destructive and cannot be undone."),
mcp.WithToolAnnotation(annotation.Destructive("Delete a tag")), mcp.WithToolAnnotation(annotation.Destructive("Delete a tag")),
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)),
@@ -43,6 +44,7 @@ var (
GetTagTool = mcp.NewTool( GetTagTool = mcp.NewTool(
GetTagToolName, GetTagToolName,
mcp.WithDescription("Get details for a single tag in a repository by name."),
mcp.WithToolAnnotation(annotation.ReadOnly("Get tag details")), mcp.WithToolAnnotation(annotation.ReadOnly("Get tag details")),
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)),
@@ -51,6 +53,7 @@ var (
ListTagsTool = mcp.NewTool( ListTagsTool = mcp.NewTool(
ListTagsToolName, ListTagsToolName,
mcp.WithDescription("List all tags in a repository, paginated."),
mcp.WithToolAnnotation(annotation.ReadOnly("List tags")), mcp.WithToolAnnotation(annotation.ReadOnly("List tags")),
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)),
@@ -79,7 +82,6 @@ func init() {
} }
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")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -100,20 +102,19 @@ func CreateTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRes
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))
} }
_, _, err = client.CreateTag(owner, repo, gitea_sdk.CreateTagOption{ _, _, err = client.Repositories.CreateTag(ctx, owner, repo, gitea_sdk.CreateTagOption{
TagName: tagName, TagName: tagName,
Target: target, Target: target,
Message: message, Message: message,
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("create tag error: %v", err) return to.ErrorResult(fmt.Errorf("create tag error: %v", err))
} }
return mcp.NewToolResultText("Tag Created"), nil return to.TextResult("Tag Created")
} }
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")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -132,16 +133,15 @@ func DeleteTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRes
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))
} }
_, err = client.DeleteTag(owner, repo, tagName) _, err = client.Repositories.DeleteTag(ctx, owner, repo, tagName)
if err != nil { if err != nil {
return nil, fmt.Errorf("delete tag error: %v", err) return to.ErrorResult(fmt.Errorf("delete tag error: %v", err))
} }
return to.TextResult("Tag deleted") return to.TextResult("Tag deleted")
} }
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")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -160,16 +160,15 @@ func GetTagFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult
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))
} }
tag, _, err := client.GetTag(owner, repo, tagName) tag, _, err := client.Repositories.GetTag(ctx, owner, repo, tagName)
if err != nil { if err != nil {
return nil, fmt.Errorf("get tag error: %v", err) return to.ErrorResult(fmt.Errorf("get tag error: %v", err))
} }
return to.TextResult(slimTag(tag)) return to.TextResult(slimTag(tag))
} }
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")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -186,14 +185,14 @@ func ListTagsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResu
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))
} }
tags, _, err := client.ListRepoTags(owner, repo, gitea_sdk.ListRepoTagsOptions{ tags, _, err := client.Repositories.ListRepoTags(ctx, owner, repo, gitea_sdk.ListRepoTagsOptions{
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{
Page: int(page), Page: int(page),
PageSize: int(pageSize), PageSize: int(pageSize),
}, },
}) })
if err != nil { if err != nil {
return nil, fmt.Errorf("list tags error: %v", err) return to.ErrorResult(fmt.Errorf("list tags error: %v", err))
} }
return to.TextResult(slimTags(tags)) return to.TextResult(slimTags(tags))
+3 -4
View File
@@ -6,11 +6,10 @@ import (
"gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/annotation"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "gitea.com/gitea/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/to"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "gitea.dev/sdk"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"
) )
@@ -21,6 +20,7 @@ const (
var GetRepoTreeTool = mcp.NewTool( var GetRepoTreeTool = mcp.NewTool(
GetRepoTreeToolName, GetRepoTreeToolName,
mcp.WithDescription("Get the file tree of a repository at a given ref (SHA, branch, or tag), optionally recursively."),
mcp.WithToolAnnotation(annotation.ReadOnly("Get repository file tree")), mcp.WithToolAnnotation(annotation.ReadOnly("Get repository file tree")),
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)),
@@ -38,7 +38,6 @@ func init() {
} }
func GetRepoTreeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetRepoTreeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetRepoTreeFn")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -67,7 +66,7 @@ func GetRepoTreeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
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))
} }
tree, _, err := client.GetTrees(owner, repo, opt) tree, _, err := client.Git.GetTrees(ctx, owner, repo, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get repository tree err: %v", err)) return to.ErrorResult(fmt.Errorf("get repository tree err: %v", err))
} }
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"slices" "slices"
"testing" "testing"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "gitea.dev/sdk"
) )
func TestSlimTree(t *testing.T) { func TestSlimTree(t *testing.T) {
+19 -30
View File
@@ -7,12 +7,12 @@ import (
"gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/annotation"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "gitea.com/gitea/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/slim"
"gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/to"
"gitea.com/gitea/gitea-mcp/pkg/tool" "gitea.com/gitea/gitea-mcp/pkg/tool"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "gitea.dev/sdk"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"
) )
@@ -29,6 +29,7 @@ const (
var ( var (
SearchUsersTool = mcp.NewTool( SearchUsersTool = mcp.NewTool(
SearchUsersToolName, SearchUsersToolName,
mcp.WithDescription("Search for Gitea users by username or full name."),
mcp.WithToolAnnotation(annotation.ReadOnly("Search users")), mcp.WithToolAnnotation(annotation.ReadOnly("Search users")),
mcp.WithString("query", mcp.Required()), mcp.WithString("query", mcp.Required()),
mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1)), mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1)),
@@ -37,6 +38,7 @@ var (
SearOrgTeamsTool = mcp.NewTool( SearOrgTeamsTool = mcp.NewTool(
SearchOrgTeamsToolName, SearchOrgTeamsToolName,
mcp.WithDescription("Search for teams within an organization by name, optionally including each team's description in the results."),
mcp.WithToolAnnotation(annotation.ReadOnly("Search organization teams")), mcp.WithToolAnnotation(annotation.ReadOnly("Search organization teams")),
mcp.WithString("org", mcp.Required()), mcp.WithString("org", mcp.Required()),
mcp.WithString("query", mcp.Required()), mcp.WithString("query", mcp.Required()),
@@ -47,6 +49,7 @@ var (
SearchReposTool = mcp.NewTool( SearchReposTool = mcp.NewTool(
SearchReposToolName, SearchReposToolName,
mcp.WithDescription("Search for repositories by keyword, with filters for topic/description matching, owner, visibility, archived status, and sort order."),
mcp.WithToolAnnotation(annotation.ReadOnly("Search repositories")), mcp.WithToolAnnotation(annotation.ReadOnly("Search repositories")),
mcp.WithString("query", mcp.Required()), mcp.WithString("query", mcp.Required()),
mcp.WithBoolean("keywordIsTopic"), mcp.WithBoolean("keywordIsTopic"),
@@ -94,7 +97,6 @@ func init() {
} }
func UsersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func UsersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called UsersFn")
keyword, err := params.GetString(req.GetArguments(), "query") keyword, err := params.GetString(req.GetArguments(), "query")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -111,7 +113,7 @@ func UsersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult,
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))
} }
users, _, err := client.SearchUsers(opt) users, _, err := client.Users.SearchUsers(ctx, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("search users err: %v", err)) return to.ErrorResult(fmt.Errorf("search users err: %v", err))
} }
@@ -119,7 +121,6 @@ func UsersFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult,
} }
func OrgTeamsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func OrgTeamsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called OrgTeamsFn")
org, err := params.GetString(req.GetArguments(), "org") org, err := params.GetString(req.GetArguments(), "org")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -142,7 +143,7 @@ func OrgTeamsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResu
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))
} }
teams, _, err := client.SearchOrgTeams(org, &opt) teams, _, err := client.Organizations.SearchOrgTeams(ctx, org, &opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("search organization teams error: %v", err)) return to.ErrorResult(fmt.Errorf("search organization teams error: %v", err))
} }
@@ -150,34 +151,23 @@ func OrgTeamsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResu
} }
func ReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func ReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called ReposFn")
keyword, err := params.GetString(req.GetArguments(), "query") keyword, err := params.GetString(req.GetArguments(), "query")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
} }
keywordIsTopic, _ := req.GetArguments()["keywordIsTopic"].(bool) args := req.GetArguments()
keywordInDescription, _ := req.GetArguments()["keywordInDescription"].(bool) keywordIsTopic, _ := args["keywordIsTopic"].(bool)
ownerID := params.GetOptionalInt(req.GetArguments(), "ownerID", 0) keywordInDescription, _ := args["keywordInDescription"].(bool)
var pIsPrivate *bool sort, _ := args["sort"].(string)
isPrivate, ok := req.GetArguments()["isPrivate"].(bool) order, _ := args["order"].(string)
if ok { page, pageSize := params.GetPagination(args, 30)
pIsPrivate = new(isPrivate)
}
var pIsArchived *bool
isArchived, ok := req.GetArguments()["isArchived"].(bool)
if ok {
pIsArchived = new(isArchived)
}
sort, _ := req.GetArguments()["sort"].(string)
order, _ := req.GetArguments()["order"].(string)
page, pageSize := params.GetPagination(req.GetArguments(), 30)
opt := gitea_sdk.SearchRepoOptions{ opt := gitea_sdk.SearchRepoOptions{
Keyword: keyword, Keyword: keyword,
KeywordIsTopic: keywordIsTopic, KeywordIsTopic: keywordIsTopic,
KeywordInDescription: keywordInDescription, KeywordInDescription: keywordInDescription,
OwnerID: ownerID, OwnerID: params.GetOptionalInt(args, "ownerID", 0),
IsPrivate: pIsPrivate, IsPrivate: params.GetOptionalBoolPtr(args, "isPrivate"),
IsArchived: pIsArchived, IsArchived: params.GetOptionalBoolPtr(args, "isArchived"),
Sort: sort, Sort: sort,
Order: order, Order: order,
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{
@@ -189,15 +179,14 @@ func ReposFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult,
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))
} }
repos, _, err := client.SearchRepos(opt) repos, _, err := client.Repositories.SearchRepos(ctx, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("search repos error: %v", err)) return to.ErrorResult(fmt.Errorf("search repos error: %v", err))
} }
return to.TextResult(slimRepos(repos)) return to.TextResult(slim.Repos(repos))
} }
func IssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func IssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called IssuesFn")
args := req.GetArguments() args := req.GetArguments()
query, err := params.GetString(args, "query") query, err := params.GetString(args, "query")
if err != nil { if err != nil {
@@ -228,7 +217,7 @@ func IssuesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult
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))
} }
issues, _, err := client.ListIssues(opt) issues, _, err := client.Issues.ListIssues(ctx, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("search issues err: %v", err)) return to.ErrorResult(fmt.Errorf("search issues err: %v", err))
} }
+6 -79
View File
@@ -1,28 +1,15 @@
package search package search
import ( import (
gitea_sdk "code.gitea.io/sdk/gitea" "gitea.com/gitea/gitea-mcp/pkg/slim"
)
func slimUserDetail(u *gitea_sdk.User) map[string]any { gitea_sdk "gitea.dev/sdk"
if u == nil { )
return nil
}
return map[string]any{
"id": u.ID,
"login": u.UserName,
"full_name": u.FullName,
"email": u.Email,
"avatar_url": u.AvatarURL,
"html_url": u.HTMLURL,
"is_admin": u.IsAdmin,
}
}
func slimUserDetails(users []*gitea_sdk.User) []map[string]any { func slimUserDetails(users []*gitea_sdk.User) []map[string]any {
out := make([]map[string]any, 0, len(users)) out := make([]map[string]any, 0, len(users))
for _, u := range users { for _, u := range users {
out = append(out, slimUserDetail(u)) out = append(out, slim.UserDetail(u))
} }
return out return out
} }
@@ -47,66 +34,6 @@ func slimTeams(teams []*gitea_sdk.Team) []map[string]any {
return out return out
} }
func slimRepo(r *gitea_sdk.Repository) map[string]any {
if r == nil {
return nil
}
m := map[string]any{
"id": r.ID,
"full_name": r.FullName,
"description": r.Description,
"html_url": r.HTMLURL,
"clone_url": r.CloneURL,
"ssh_url": r.SSHURL,
"default_branch": r.DefaultBranch,
"private": r.Private,
"fork": r.Fork,
"archived": r.Archived,
"language": r.Language,
"stars_count": r.Stars,
"forks_count": r.Forks,
"open_issues_count": r.OpenIssues,
"open_pr_counter": r.OpenPulls,
"created_at": r.Created,
"updated_at": r.Updated,
}
if r.Owner != nil {
m["owner"] = r.Owner.UserName
}
if len(r.Topics) > 0 {
m["topics"] = r.Topics
}
return m
}
func slimRepos(repos []*gitea_sdk.Repository) []map[string]any {
out := make([]map[string]any, 0, len(repos))
for _, r := range repos {
out = append(out, slimRepo(r))
}
return out
}
func userLogin(u *gitea_sdk.User) string {
if u == nil {
return ""
}
return u.UserName
}
func labelNames(labels []*gitea_sdk.Label) []string {
if len(labels) == 0 {
return nil
}
out := make([]string, 0, len(labels))
for _, l := range labels {
if l != nil {
out = append(out, l.Name)
}
}
return out
}
func slimIssues(issues []*gitea_sdk.Issue) []map[string]any { func slimIssues(issues []*gitea_sdk.Issue) []map[string]any {
out := make([]map[string]any, 0, len(issues)) out := make([]map[string]any, 0, len(issues))
for _, i := range issues { for _, i := range issues {
@@ -118,13 +45,13 @@ func slimIssues(issues []*gitea_sdk.Issue) []map[string]any {
"title": i.Title, "title": i.Title,
"state": i.State, "state": i.State,
"html_url": i.HTMLURL, "html_url": i.HTMLURL,
"user": userLogin(i.Poster), "user": slim.UserLogin(i.Poster),
"comments": i.Comments, "comments": i.Comments,
"created_at": i.Created, "created_at": i.Created,
"updated_at": i.Updated, "updated_at": i.Updated,
} }
if len(i.Labels) > 0 { if len(i.Labels) > 0 {
m["labels"] = labelNames(i.Labels) m["labels"] = slim.LabelNames(i.Labels)
} }
if i.Repository != nil { if i.Repository != nil {
m["repository"] = i.Repository.FullName m["repository"] = i.Repository.FullName
+1 -1
View File
@@ -4,7 +4,7 @@ import (
"slices" "slices"
"testing" "testing"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "gitea.dev/sdk"
) )
func TestSlimIssues(t *testing.T) { func TestSlimIssues(t *testing.T) {
+1 -1
View File
@@ -1,7 +1,7 @@
package timetracking package timetracking
import ( import (
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "gitea.dev/sdk"
) )
func slimStopWatch(s *gitea_sdk.StopWatch) map[string]any { func slimStopWatch(s *gitea_sdk.StopWatch) map[string]any {
+10 -24
View File
@@ -7,12 +7,11 @@ import (
"gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/annotation"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "gitea.com/gitea/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/to"
"gitea.com/gitea/gitea-mcp/pkg/tool" "gitea.com/gitea/gitea-mcp/pkg/tool"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "gitea.dev/sdk"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"
) )
@@ -95,10 +94,7 @@ func writeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult,
} }
} }
// Stopwatch handler functions
func startStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func startStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called startStopwatchFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -115,7 +111,7 @@ func startStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
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))
} }
_, err = client.StartIssueStopWatch(owner, repo, index) _, err = client.Issues.StartIssueStopWatch(ctx, owner, repo, index)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("start stopwatch on %s/%s#%d err: %v", owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("start stopwatch on %s/%s#%d err: %v", owner, repo, index, err))
} }
@@ -123,7 +119,6 @@ func startStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
} }
func stopStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func stopStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called stopStopwatchFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -140,7 +135,7 @@ func stopStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
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))
} }
_, err = client.StopIssueStopWatch(owner, repo, index) _, err = client.Issues.StopIssueStopWatch(ctx, owner, repo, index)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("stop stopwatch on %s/%s#%d err: %v", owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("stop stopwatch on %s/%s#%d err: %v", owner, repo, index, err))
} }
@@ -148,7 +143,6 @@ func stopStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
} }
func deleteStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func deleteStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called deleteStopwatchFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -165,7 +159,7 @@ func deleteStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
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))
} }
_, err = client.DeleteIssueStopwatch(owner, repo, index) _, err = client.Issues.DeleteIssueStopwatch(ctx, owner, repo, index)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("delete stopwatch on %s/%s#%d err: %v", owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("delete stopwatch on %s/%s#%d err: %v", owner, repo, index, err))
} }
@@ -173,12 +167,11 @@ func deleteStopwatchFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallT
} }
func getMyStopwatchesFn(ctx context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) { func getMyStopwatchesFn(ctx context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called getMyStopwatchesFn")
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))
} }
stopwatches, _, err := client.ListMyStopwatches(gitea_sdk.ListStopwatchesOptions{}) stopwatches, _, err := client.Issues.ListMyStopwatches(ctx, gitea_sdk.ListStopwatchesOptions{})
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get stopwatches err: %v", err)) return to.ErrorResult(fmt.Errorf("get stopwatches err: %v", err))
} }
@@ -188,10 +181,7 @@ func getMyStopwatchesFn(ctx context.Context, _ mcp.CallToolRequest) (*mcp.CallTo
return to.TextResult(slimStopWatches(stopwatches)) return to.TextResult(slimStopWatches(stopwatches))
} }
// Tracked time handler functions
func listTrackedTimesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func listTrackedTimesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called listTrackedTimesFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -210,7 +200,7 @@ func listTrackedTimesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err)) return to.ErrorResult(fmt.Errorf("get gitea client err: %v", err))
} }
times, _, err := client.ListIssueTrackedTimes(owner, repo, index, gitea_sdk.ListTrackedTimesOptions{ times, _, err := client.Issues.ListIssueTrackedTimes(ctx, owner, repo, index, gitea_sdk.ListTrackedTimesOptions{
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{
Page: page, Page: page,
PageSize: pageSize, PageSize: pageSize,
@@ -226,7 +216,6 @@ func listTrackedTimesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
} }
func addTrackedTimeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func addTrackedTimeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called addTrackedTimeFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -248,7 +237,7 @@ func addTrackedTimeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
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))
} }
trackedTime, _, err := client.AddTime(owner, repo, index, gitea_sdk.AddTimeOption{ trackedTime, _, err := client.Issues.AddTime(ctx, owner, repo, index, gitea_sdk.AddTimeOption{
Time: timeSeconds, Time: timeSeconds,
}) })
if err != nil { if err != nil {
@@ -258,7 +247,6 @@ func addTrackedTimeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
} }
func deleteTrackedTimeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func deleteTrackedTimeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called deleteTrackedTimeFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -280,7 +268,7 @@ func deleteTrackedTimeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Cal
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))
} }
_, err = client.DeleteTime(owner, repo, index, id) _, err = client.Issues.DeleteTime(ctx, owner, repo, index, id)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("delete tracked time %d from %s/%s#%d err: %v", id, owner, repo, index, err)) return to.ErrorResult(fmt.Errorf("delete tracked time %d from %s/%s#%d err: %v", id, owner, repo, index, err))
} }
@@ -288,7 +276,6 @@ func deleteTrackedTimeFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Cal
} }
func listRepoTimesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func listRepoTimesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called listRepoTimesFn")
owner, err := params.GetString(req.GetArguments(), "owner") owner, err := params.GetString(req.GetArguments(), "owner")
if err != nil { if err != nil {
return to.ErrorResult(err) return to.ErrorResult(err)
@@ -303,7 +290,7 @@ func listRepoTimesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
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))
} }
times, _, err := client.ListRepoTrackedTimes(owner, repo, gitea_sdk.ListTrackedTimesOptions{ times, _, err := client.Issues.ListRepoTrackedTimes(ctx, owner, repo, gitea_sdk.ListTrackedTimesOptions{
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{
Page: page, Page: page,
PageSize: pageSize, PageSize: pageSize,
@@ -319,12 +306,11 @@ func listRepoTimesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
} }
func getMyTimesFn(ctx context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) { func getMyTimesFn(ctx context.Context, _ mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called getMyTimesFn")
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))
} }
times, _, err := client.ListMyTrackedTimes(gitea_sdk.ListTrackedTimesOptions{}) times, _, err := client.Issues.ListMyTrackedTimes(ctx, gitea_sdk.ListTrackedTimesOptions{})
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get tracked times err: %v", err)) return to.ErrorResult(fmt.Errorf("get tracked times err: %v", err))
} }
+1 -16
View File
@@ -1,24 +1,9 @@
package user package user
import ( import (
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "gitea.dev/sdk"
) )
func slimUserDetail(u *gitea_sdk.User) map[string]any {
if u == nil {
return nil
}
return map[string]any{
"id": u.ID,
"login": u.UserName,
"full_name": u.FullName,
"email": u.Email,
"avatar_url": u.AvatarURL,
"html_url": u.HTMLURL,
"is_admin": u.IsAdmin,
}
}
func slimOrg(o *gitea_sdk.Organization) map[string]any { func slimOrg(o *gitea_sdk.Organization) map[string]any {
if o == nil { if o == nil {
return nil return nil
-39
View File
@@ -1,39 +0,0 @@
package user
import (
"testing"
gitea_sdk "code.gitea.io/sdk/gitea"
)
func TestSlimUserDetail(t *testing.T) {
u := &gitea_sdk.User{
ID: 42,
UserName: "alice",
FullName: "Alice Smith",
Email: "alice@example.com",
AvatarURL: "https://gitea.com/avatars/42",
HTMLURL: "https://gitea.com/alice",
IsAdmin: true,
}
m := slimUserDetail(u)
if m["id"] != int64(42) {
t.Errorf("expected id 42, got %v", m["id"])
}
if m["login"] != "alice" {
t.Errorf("expected login alice, got %v", m["login"])
}
if m["full_name"] != "Alice Smith" {
t.Errorf("expected full_name Alice Smith, got %v", m["full_name"])
}
if m["is_admin"] != true {
t.Errorf("expected is_admin true, got %v", m["is_admin"])
}
}
func TestSlimUserDetail_Nil(t *testing.T) {
if m := slimUserDetail(nil); m != nil {
t.Errorf("expected nil for nil user, got %v", m)
}
}
+10 -42
View File
@@ -6,90 +6,58 @@ import (
"gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/annotation"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "gitea.com/gitea/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/slim"
"gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/to"
"gitea.com/gitea/gitea-mcp/pkg/tool" "gitea.com/gitea/gitea-mcp/pkg/tool"
gitea_sdk "code.gitea.io/sdk/gitea" gitea_sdk "gitea.dev/sdk"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server" "github.com/mark3labs/mcp-go/server"
) )
const ( const (
// GetMyUserInfoToolName is the unique tool name used for MCP registration and lookup of the get_me command.
GetMyUserInfoToolName = "get_me" GetMyUserInfoToolName = "get_me"
// GetUserOrgsToolName is the unique tool name used for MCP registration and lookup of the get_user_orgs command.
GetUserOrgsToolName = "get_user_orgs" GetUserOrgsToolName = "get_user_orgs"
// defaultPage is the default starting page number used for paginated organization listings.
defaultPage = 1
// defaultPageSize is the default number of organizations per page for paginated queries.
defaultPageSize = 30
) )
// Tool is the MCP tool manager instance for registering all MCP tools in this package.
var Tool = tool.New() var Tool = tool.New()
var ( var (
// GetMyUserInfoTool is the MCP tool for retrieving the current user's info.
// It is registered with a specific name and a description string.
GetMyUserInfoTool = mcp.NewTool( GetMyUserInfoTool = mcp.NewTool(
GetMyUserInfoToolName, GetMyUserInfoToolName,
mcp.WithDescription("Get current user"), mcp.WithDescription("Get current user"),
mcp.WithToolAnnotation(annotation.ReadOnly("Get current user information")), mcp.WithToolAnnotation(annotation.ReadOnly("Get current user information")),
) )
// GetUserOrgsTool is the MCP tool for listing organizations for the authenticated user.
// It supports pagination via "page" and "per_page" arguments with default values specified above.
GetUserOrgsTool = mcp.NewTool( GetUserOrgsTool = mcp.NewTool(
GetUserOrgsToolName, GetUserOrgsToolName,
mcp.WithDescription("List current user's organizations"), mcp.WithDescription("List current user's organizations"),
mcp.WithToolAnnotation(annotation.ReadOnly("Get user organizations")), mcp.WithToolAnnotation(annotation.ReadOnly("Get user organizations")),
mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(defaultPage)), mcp.WithNumber("page", mcp.Description(params.PageDesc), mcp.DefaultNumber(1)),
mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(defaultPageSize)), mcp.WithNumber("per_page", mcp.Description(params.PaginationDesc), mcp.DefaultNumber(30)),
) )
) )
// init registers all MCP tools in Tool at package initialization.
// This function ensures the handler functions are registered before server usage.
func init() { func init() {
registerTools() Tool.RegisterRead(server.ServerTool{Tool: GetMyUserInfoTool, Handler: GetUserInfoFn})
Tool.RegisterRead(server.ServerTool{Tool: GetUserOrgsTool, Handler: GetUserOrgsFn})
} }
// registerTools registers all local MCP tool definitions and their handler functions.
// To add new functionality, append your tool/handler pair to the tools slice below.
func registerTools() {
tools := []server.ServerTool{
{Tool: GetMyUserInfoTool, Handler: GetUserInfoFn},
{Tool: GetUserOrgsTool, Handler: GetUserOrgsFn},
}
for _, t := range tools {
Tool.RegisterRead(t)
}
}
// GetUserInfoFn is the handler for "get_me" MCP tool requests.
// Logs invocation, fetches current user info from gitea, wraps result for MCP.
func GetUserInfoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetUserInfoFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("[User] Called GetUserInfoFn")
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))
} }
user, _, err := client.GetMyUserInfo() user, _, err := client.Users.GetMyUserInfo(ctx)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get user info err: %v", err)) return to.ErrorResult(fmt.Errorf("get user info err: %v", err))
} }
return to.TextResult(slimUserDetail(user)) return to.TextResult(slim.UserDetail(user))
} }
// GetUserOrgsFn is the handler for "get_user_orgs" MCP tool requests.
// Logs invocation, pulls validated pagination arguments from request,
// performs Gitea organization listing, and wraps the result for MCP.
func GetUserOrgsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetUserOrgsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("[User] Called GetUserOrgsFn") page, pageSize := params.GetPagination(req.GetArguments(), 30)
page, pageSize := params.GetPagination(req.GetArguments(), defaultPageSize)
opt := gitea_sdk.ListOrgsOptions{ opt := gitea_sdk.ListOrgsOptions{
ListOptions: gitea_sdk.ListOptions{ ListOptions: gitea_sdk.ListOptions{
@@ -101,7 +69,7 @@ func GetUserOrgsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
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))
} }
orgs, _, err := client.ListMyOrgs(opt) orgs, _, err := client.Organizations.ListMyOrgs(ctx, opt)
if err != nil { if err != nil {
return to.ErrorResult(fmt.Errorf("get user orgs err: %v", err)) return to.ErrorResult(fmt.Errorf("get user orgs err: %v", err))
} }
+1 -2
View File
@@ -6,7 +6,6 @@ import (
"gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/annotation"
"gitea.com/gitea/gitea-mcp/pkg/flag" "gitea.com/gitea/gitea-mcp/pkg/flag"
"gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/to"
"gitea.com/gitea/gitea-mcp/pkg/tool" "gitea.com/gitea/gitea-mcp/pkg/tool"
@@ -22,6 +21,7 @@ const (
var GetGiteaMCPServerVersionTool = mcp.NewTool( var GetGiteaMCPServerVersionTool = mcp.NewTool(
GetGiteaMCPServerVersion, GetGiteaMCPServerVersion,
mcp.WithDescription("Get the running version of the Gitea MCP Server itself (not the Gitea instance it connects to)."),
mcp.WithToolAnnotation(annotation.ReadOnly("Get server version")), mcp.WithToolAnnotation(annotation.ReadOnly("Get server version")),
) )
@@ -33,7 +33,6 @@ func init() {
} }
func GetGiteaMCPServerVersionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func GetGiteaMCPServerVersionFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called GetGiteaMCPServerVersionFn")
version := flag.Version version := flag.Version
if version == "" { if version == "" {
version = "dev" version = "dev"
-7
View File
@@ -8,7 +8,6 @@ import (
"gitea.com/gitea/gitea-mcp/pkg/annotation" "gitea.com/gitea/gitea-mcp/pkg/annotation"
"gitea.com/gitea/gitea-mcp/pkg/gitea" "gitea.com/gitea/gitea-mcp/pkg/gitea"
"gitea.com/gitea/gitea-mcp/pkg/log"
"gitea.com/gitea/gitea-mcp/pkg/params" "gitea.com/gitea/gitea-mcp/pkg/params"
"gitea.com/gitea/gitea-mcp/pkg/to" "gitea.com/gitea/gitea-mcp/pkg/to"
"gitea.com/gitea/gitea-mcp/pkg/tool" "gitea.com/gitea/gitea-mcp/pkg/tool"
@@ -95,7 +94,6 @@ func wikiWriteFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolRes
} }
func listWikiPagesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func listWikiPagesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called listWikiPagesFn")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -116,7 +114,6 @@ func listWikiPagesFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToo
} }
func getWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func getWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called getWikiPageFn")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -141,7 +138,6 @@ func getWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolR
} }
func getWikiRevisionsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func getWikiRevisionsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called getWikiRevisionsFn")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -166,7 +162,6 @@ func getWikiRevisionsFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.Call
} }
func createWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func createWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called createWikiPageFn")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -206,7 +201,6 @@ func createWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
} }
func updateWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func updateWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called updateWikiPageFn")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
@@ -252,7 +246,6 @@ func updateWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallTo
} }
func deleteWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) { func deleteWikiPageFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
log.Debugf("Called deleteWikiPageFn")
args := req.GetArguments() args := req.GetArguments()
owner, err := params.GetString(args, "owner") owner, err := params.GetString(args, "owner")
if err != nil { if err != nil {
-4
View File
@@ -1,21 +1,17 @@
// Package annotation provides shared MCP tool annotation helpers.
package annotation package annotation
import "github.com/mark3labs/mcp-go/mcp" import "github.com/mark3labs/mcp-go/mcp"
// ReadOnly returns a ToolAnnotation for read-only tools.
func ReadOnly(title string) mcp.ToolAnnotation { func ReadOnly(title string) mcp.ToolAnnotation {
t := true t := true
return mcp.ToolAnnotation{Title: title, ReadOnlyHint: &t} return mcp.ToolAnnotation{Title: title, ReadOnlyHint: &t}
} }
// Write returns a ToolAnnotation for write tools.
func Write(title string) mcp.ToolAnnotation { func Write(title string) mcp.ToolAnnotation {
f := false f := false
return mcp.ToolAnnotation{Title: title, ReadOnlyHint: &f} return mcp.ToolAnnotation{Title: title, ReadOnlyHint: &f}
} }
// Destructive returns a ToolAnnotation for destructive write tools.
func Destructive(title string) mcp.ToolAnnotation { func Destructive(title string) mcp.ToolAnnotation {
f, t := false, true f, t := false, true
return mcp.ToolAnnotation{Title: title, ReadOnlyHint: &f, DestructiveHint: &t} return mcp.ToolAnnotation{Title: title, ReadOnlyHint: &f, DestructiveHint: &t}
+33 -14
View File
@@ -6,28 +6,47 @@ import (
"errors" "errors"
"fmt" "fmt"
"net/http" "net/http"
"sync"
mcpContext "gitea.com/gitea/gitea-mcp/pkg/context" mcpContext "gitea.com/gitea/gitea-mcp/pkg/context"
"gitea.com/gitea/gitea-mcp/pkg/flag" "gitea.com/gitea/gitea-mcp/pkg/flag"
"code.gitea.io/sdk/gitea" "gitea.dev/sdk"
) )
func NewClient(token string) (*gitea.Client, error) { var (
httpClient := &http.Client{ clientCache sync.Map // token -> *gitea.Client
Transport: http.DefaultTransport, sharedTransOnce sync.Once
CheckRedirect: checkRedirect, sharedTrans *http.Transport
)
func sharedTransport() *http.Transport {
sharedTransOnce.Do(func() {
sharedTrans = http.DefaultTransport.(*http.Transport).Clone()
if flag.Insecure {
sharedTrans.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} //nolint:gosec // user-requested insecure mode
}
})
return sharedTrans
} }
// NewClient returns a cached *gitea.Client keyed by host+token. The SDK's per-client
// version cache and the shared transport let us reuse keep-alive connections
// and avoid the SDK's /api/v1/version preflight on every tool call.
func NewClient(token string) (*gitea.Client, error) {
key := flag.Host + "\x00" + token
if v, ok := clientCache.Load(key); ok {
return v.(*gitea.Client), nil
}
httpClient := &http.Client{
Transport: sharedTransport(),
CheckRedirect: checkRedirect,
}
opts := []gitea.ClientOption{ opts := []gitea.ClientOption{
gitea.SetToken(token), gitea.SetToken(token),
gitea.SetHTTPClient(httpClient),
} }
if flag.Insecure {
httpClient.Transport.(*http.Transport).TLSClientConfig = &tls.Config{
InsecureSkipVerify: true,
}
}
opts = append(opts, gitea.SetHTTPClient(httpClient))
if flag.Debug { if flag.Debug {
opts = append(opts, gitea.SetDebugMode()) opts = append(opts, gitea.SetDebugMode())
} }
@@ -35,10 +54,10 @@ func NewClient(token string) (*gitea.Client, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("create gitea client err: %w", err) return nil, fmt.Errorf("create gitea client err: %w", err)
} }
// Set user agent for the client
client.SetUserAgent("gitea-mcp-server/" + flag.Version) client.SetUserAgent("gitea-mcp-server/" + flag.Version)
return client, nil
actual, _ := clientCache.LoadOrStore(key, client)
return actual.(*gitea.Client), nil
} }
// checkRedirect prevents Go from silently changing mutating requests (POST, PATCH, etc.) // checkRedirect prevents Go from silently changing mutating requests (POST, PATCH, etc.)
+23 -14
View File
@@ -3,7 +3,6 @@ package gitea
import ( import (
"bytes" "bytes"
"context" "context"
"crypto/tls"
"encoding/json" "encoding/json"
"errors" "errors"
"fmt" "fmt"
@@ -11,12 +10,18 @@ import (
"net/http" "net/http"
"net/url" "net/url"
"strings" "strings"
"sync"
"time" "time"
mcpContext "gitea.com/gitea/gitea-mcp/pkg/context" mcpContext "gitea.com/gitea/gitea-mcp/pkg/context"
"gitea.com/gitea/gitea-mcp/pkg/flag" "gitea.com/gitea/gitea-mcp/pkg/flag"
) )
const (
httpClientTimeout = 60 * time.Second
errBodySnippetSize = 8192
)
type HTTPError struct { type HTTPError struct {
StatusCode int StatusCode int
Body string Body string
@@ -38,16 +43,20 @@ func tokenFromContext(ctx context.Context) string {
return flag.Token return flag.Token
} }
func newRESTHTTPClient() *http.Client { var (
transport := http.DefaultTransport.(*http.Transport).Clone() restClientOnce sync.Once
if flag.Insecure { restClient *http.Client
transport.TLSClientConfig = &tls.Config{InsecureSkipVerify: true} //nolint:gosec // user-requested insecure mode )
}
return &http.Client{ func restHTTPClient() *http.Client {
Transport: transport, restClientOnce.Do(func() {
Timeout: 60 * time.Second, restClient = &http.Client{
Transport: sharedTransport(),
Timeout: httpClientTimeout,
CheckRedirect: checkRedirect, CheckRedirect: checkRedirect,
} }
})
return restClient
} }
func buildAPIURL(path string, query url.Values) (string, error) { func buildAPIURL(path string, query url.Values) (string, error) {
@@ -96,7 +105,7 @@ func DoJSON(ctx context.Context, method, path string, query url.Values, body, re
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
} }
client := newRESTHTTPClient() client := restHTTPClient()
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return 0, fmt.Errorf("do request: %w", err) return 0, fmt.Errorf("do request: %w", err)
@@ -104,7 +113,7 @@ func DoJSON(ctx context.Context, method, path string, query url.Values, body, re
defer resp.Body.Close() defer resp.Body.Close()
if resp.StatusCode < 200 || resp.StatusCode >= 300 { if resp.StatusCode < 200 || resp.StatusCode >= 300 {
bodySnippet, _ := io.ReadAll(io.LimitReader(resp.Body, 8192)) bodySnippet, _ := io.ReadAll(io.LimitReader(resp.Body, errBodySnippetSize))
return resp.StatusCode, &HTTPError{StatusCode: resp.StatusCode, Body: strings.TrimSpace(string(bodySnippet))} return resp.StatusCode, &HTTPError{StatusCode: resp.StatusCode, Body: strings.TrimSpace(string(bodySnippet))}
} }
@@ -151,7 +160,7 @@ func DoBytes(ctx context.Context, method, path string, query url.Values, body an
req.Header.Set("Content-Type", "application/json") req.Header.Set("Content-Type", "application/json")
} }
client := newRESTHTTPClient() client := restHTTPClient()
resp, err := client.Do(req) resp, err := client.Do(req)
if err != nil { if err != nil {
return nil, 0, fmt.Errorf("do request: %w", err) return nil, 0, fmt.Errorf("do request: %w", err)
@@ -165,8 +174,8 @@ func DoBytes(ctx context.Context, method, path string, query url.Values, body an
if resp.StatusCode < 200 || resp.StatusCode >= 300 { if resp.StatusCode < 200 || resp.StatusCode >= 300 {
bodySnippet := respBytes bodySnippet := respBytes
if len(bodySnippet) > 8192 { if len(bodySnippet) > errBodySnippetSize {
bodySnippet = bodySnippet[:8192] bodySnippet = bodySnippet[:errBodySnippetSize]
} }
return nil, resp.StatusCode, &HTTPError{StatusCode: resp.StatusCode, Body: strings.TrimSpace(string(bodySnippet))} return nil, resp.StatusCode, &HTTPError{StatusCode: resp.StatusCode, Body: strings.TrimSpace(string(bodySnippet))}
} }
-18
View File
@@ -79,24 +79,6 @@ func SetDefault(logger *zap.Logger) {
} }
} }
func New() *Logger {
return &Logger{
defaultLogger: Default(),
}
}
type Logger struct {
defaultLogger *zap.Logger
}
func (l *Logger) Infof(msg string, args ...any) {
l.defaultLogger.Sugar().Infof(msg, args...)
}
func (l *Logger) Errorf(msg string, args ...any) {
l.defaultLogger.Sugar().Errorf(msg, args...)
}
func Debug(msg string, fields ...zap.Field) { func Debug(msg string, fields ...zap.Field) {
Default().Debug(msg, fields...) Default().Debug(msg, fields...)
} }
+33 -16
View File
@@ -16,16 +16,15 @@ const (
PaginationDesc = "results per page" PaginationDesc = "results per page"
) )
// GetString extracts a required string parameter from MCP tool arguments. // GetString extracts a required string parameter. Empty strings are treated as missing.
func GetString(args map[string]any, key string) (string, error) { func GetString(args map[string]any, key string) (string, error) {
val, ok := args[key].(string) val, ok := args[key].(string)
if !ok { if !ok || val == "" {
return "", fmt.Errorf("%s is required", key) return "", fmt.Errorf("%s is required", key)
} }
return val, nil return val, nil
} }
// GetOptionalString extracts an optional string parameter with a default value.
func GetOptionalString(args map[string]any, key, defaultVal string) string { func GetOptionalString(args map[string]any, key, defaultVal string) string {
if val, ok := args[key].(string); ok { if val, ok := args[key].(string); ok {
return val return val
@@ -33,7 +32,6 @@ func GetOptionalString(args map[string]any, key, defaultVal string) string {
return defaultVal return defaultVal
} }
// GetStringSlice extracts an optional string slice parameter from MCP tool arguments.
func GetStringSlice(args map[string]any, key string) []string { func GetStringSlice(args map[string]any, key string) []string {
val, ok := args[key] val, ok := args[key]
if !ok { if !ok {
@@ -52,13 +50,11 @@ func GetStringSlice(args map[string]any, key string) []string {
return out return out
} }
// GetPagination extracts page and per_page parameters, returning them as ints.
func GetPagination(args map[string]any, defaultPageSize int64) (page, pageSize int) { func GetPagination(args map[string]any, defaultPageSize int64) (page, pageSize int) {
return int(GetOptionalInt(args, "page", 1)), int(GetOptionalInt(args, "per_page", defaultPageSize)) return int(GetOptionalInt(args, "page", 1)), int(GetOptionalInt(args, "per_page", defaultPageSize))
} }
// ToInt64 converts a value to int64, accepting both float64 (JSON number) and // ToInt64 accepts float64 (JSON number) and string representations.
// string representations. Returns false if the value cannot be converted.
func ToInt64(val any) (int64, bool) { func ToInt64(val any) (int64, bool) {
switch v := val.(type) { switch v := val.(type) {
case float64: case float64:
@@ -74,10 +70,8 @@ func ToInt64(val any) (int64, bool) {
} }
} }
// GetIndex extracts a required integer parameter from MCP tool arguments. // GetIndex extracts a required integer. Accepts numeric or string forms — LLM callers
// It accepts both numeric (float64 from JSON) and string representations. // often pass identifiers like issue/PR numbers as strings.
// This provides better UX for LLM callers that may naturally use strings
// for identifiers like issue/PR numbers.
func GetIndex(args map[string]any, key string) (int64, error) { func GetIndex(args map[string]any, key string) (int64, error) {
val, exists := args[key] val, exists := args[key]
if !exists { if !exists {
@@ -95,7 +89,6 @@ func GetIndex(args map[string]any, key string) (int64, error) {
return 0, fmt.Errorf("%s must be a number or numeric string", key) return 0, fmt.Errorf("%s must be a number or numeric string", key)
} }
// GetInt64Slice extracts a required int64 slice parameter from MCP tool arguments.
func GetInt64Slice(args map[string]any, key string) ([]int64, error) { func GetInt64Slice(args map[string]any, key string) ([]int64, error) {
raw, ok := args[key].([]any) raw, ok := args[key].([]any)
if !ok { if !ok {
@@ -112,7 +105,7 @@ func GetInt64Slice(args map[string]any, key string) ([]int64, error) {
return out, nil return out, nil
} }
// GetOptionalTime extracts an optional RFC3339 timestamp parameter, returning nil if missing or unparseable. // GetOptionalTime parses RFC3339, returning nil if missing or unparseable.
func GetOptionalTime(args map[string]any, key string) *time.Time { func GetOptionalTime(args map[string]any, key string) *time.Time {
val, ok := args[key].(string) val, ok := args[key].(string)
if !ok { if !ok {
@@ -124,9 +117,6 @@ func GetOptionalTime(args map[string]any, key string) *time.Time {
return nil return nil
} }
// GetOptionalInt extracts an optional integer parameter from MCP tool arguments.
// Returns defaultVal if the key is missing or the value cannot be parsed.
// Accepts both float64 (JSON number) and string representations.
func GetOptionalInt(args map[string]any, key string, defaultVal int64) int64 { func GetOptionalInt(args map[string]any, key string, defaultVal int64) int64 {
val, exists := args[key] val, exists := args[key]
if !exists { if !exists {
@@ -137,3 +127,30 @@ func GetOptionalInt(args map[string]any, key string, defaultVal int64) int64 {
} }
return defaultVal return defaultVal
} }
// GetOptionalBoolPtr is for SDK fields where nil/false/true are distinct (e.g. "no change" vs "set to false").
func GetOptionalBoolPtr(args map[string]any, key string) *bool {
if v, ok := args[key].(bool); ok {
return &v
}
return nil
}
// GetOptionalStringPtr returns nil when the key is missing OR the value is an empty string.
// Use this for create/fork-style fields where "" is meaningless (e.g. fork target name).
func GetOptionalStringPtr(args map[string]any, key string) *string {
if v, ok := args[key].(string); ok && v != "" {
return &v
}
return nil
}
// GetPresentStringPtr returns &v whenever the key is present as a string, including "".
// Use this for PATCH-style fields where the SDK distinguishes "no change" (nil) from
// "set to empty" (&""), e.g. clearing an issue body or label description.
func GetPresentStringPtr(args map[string]any, key string) *string {
if v, ok := args[key].(string); ok {
return &v
}
return nil
}
+36
View File
@@ -73,6 +73,42 @@ func TestGetOptionalInt(t *testing.T) {
} }
} }
func TestGetOptionalStringPtr(t *testing.T) {
if p := GetOptionalStringPtr(map[string]any{}, "k"); p != nil {
t.Errorf("missing key: got %v, want nil", p)
}
if p := GetOptionalStringPtr(map[string]any{"k": ""}, "k"); p != nil {
t.Errorf("empty string: got %v, want nil", p)
}
if p := GetOptionalStringPtr(map[string]any{"k": 42}, "k"); p != nil {
t.Errorf("non-string: got %v, want nil", p)
}
if p := GetOptionalStringPtr(map[string]any{"k": nil}, "k"); p != nil {
t.Errorf("nil value (JSON null): got %v, want nil", p)
}
if p := GetOptionalStringPtr(map[string]any{"k": "x"}, "k"); p == nil || *p != "x" {
t.Errorf("non-empty: got %v, want &\"x\"", p)
}
}
func TestGetPresentStringPtr(t *testing.T) {
if p := GetPresentStringPtr(map[string]any{}, "k"); p != nil {
t.Errorf("missing key: got %v, want nil", p)
}
if p := GetPresentStringPtr(map[string]any{"k": 42}, "k"); p != nil {
t.Errorf("non-string: got %v, want nil", p)
}
if p := GetPresentStringPtr(map[string]any{"k": nil}, "k"); p != nil {
t.Errorf("nil value (JSON null): got %v, want nil", p)
}
if p := GetPresentStringPtr(map[string]any{"k": ""}, "k"); p == nil || *p != "" {
t.Errorf("empty string: got %v, want &\"\"", p)
}
if p := GetPresentStringPtr(map[string]any{"k": "x"}, "k"); p == nil || *p != "x" {
t.Errorf("non-empty: got %v, want &\"x\"", p)
}
}
func TestGetIndex(t *testing.T) { func TestGetIndex(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
+135
View File
@@ -0,0 +1,135 @@
package slim
import (
"fmt"
"strings"
gitea_sdk "gitea.dev/sdk"
)
func UserLogin(u *gitea_sdk.User) string {
if u == nil {
return ""
}
return u.UserName
}
func UserLogins(users []*gitea_sdk.User) []string {
if len(users) == 0 {
return nil
}
out := make([]string, 0, len(users))
for _, u := range users {
if u != nil {
out = append(out, u.UserName)
}
}
return out
}
func LabelNames(labels []*gitea_sdk.Label) []string {
if len(labels) == 0 {
return nil
}
out := make([]string, 0, len(labels))
for _, l := range labels {
if l != nil {
out = append(out, l.Name)
}
}
return out
}
func BodyWithAttachments(body string, atts []*gitea_sdk.Attachment) string {
links := make([]string, 0, len(atts))
for _, a := range atts {
if a == nil || a.DownloadURL == "" {
continue
}
links = append(links, fmt.Sprintf("[%s](%s)", a.Name, a.DownloadURL))
}
if len(links) == 0 {
return body
}
joined := strings.Join(links, "\n")
if body == "" {
return joined
}
return body + "\n\n" + joined
}
func UserDetail(u *gitea_sdk.User) map[string]any {
if u == nil {
return nil
}
return map[string]any{
"id": u.ID,
"login": u.UserName,
"full_name": u.FullName,
"email": u.Email,
"avatar_url": u.AvatarURL,
"html_url": u.HTMLURL,
"is_admin": u.IsAdmin,
}
}
func Repo(r *gitea_sdk.Repository) map[string]any {
if r == nil {
return nil
}
m := map[string]any{
"id": r.ID,
"full_name": r.FullName,
"description": r.Description,
"html_url": r.HTMLURL,
"clone_url": r.CloneURL,
"ssh_url": r.SSHURL,
"default_branch": r.DefaultBranch,
"private": r.Private,
"fork": r.Fork,
"archived": r.Archived,
"language": r.Language,
"stars_count": r.Stars,
"forks_count": r.Forks,
"open_issues_count": r.OpenIssues,
"open_pr_counter": r.OpenPulls,
"created_at": r.Created,
"updated_at": r.Updated,
}
if r.Owner != nil {
m["owner"] = r.Owner.UserName
}
if len(r.Topics) > 0 {
m["topics"] = r.Topics
}
return m
}
func Repos(repos []*gitea_sdk.Repository) []map[string]any {
out := make([]map[string]any, 0, len(repos))
for _, r := range repos {
out = append(out, Repo(r))
}
return out
}
func Label(l *gitea_sdk.Label) map[string]any {
if l == nil {
return nil
}
return map[string]any{
"id": l.ID,
"name": l.Name,
"color": l.Color,
"description": l.Description,
"exclusive": l.Exclusive,
}
}
func Labels(labels []*gitea_sdk.Label) []map[string]any {
out := make([]map[string]any, 0, len(labels))
for _, l := range labels {
out = append(out, Label(l))
}
return out
}
+110
View File
@@ -0,0 +1,110 @@
package slim
import (
"testing"
gitea_sdk "gitea.dev/sdk"
)
func TestUserDetail(t *testing.T) {
u := &gitea_sdk.User{
ID: 42,
UserName: "alice",
FullName: "Alice Smith",
Email: "alice@example.com",
AvatarURL: "https://gitea.com/avatars/42",
HTMLURL: "https://gitea.com/alice",
IsAdmin: true,
}
m := UserDetail(u)
if m["id"] != int64(42) {
t.Errorf("expected id 42, got %v", m["id"])
}
if m["login"] != "alice" {
t.Errorf("expected login alice, got %v", m["login"])
}
if m["full_name"] != "Alice Smith" {
t.Errorf("expected full_name Alice Smith, got %v", m["full_name"])
}
if m["is_admin"] != true {
t.Errorf("expected is_admin true, got %v", m["is_admin"])
}
}
func TestUserDetail_Nil(t *testing.T) {
if m := UserDetail(nil); m != nil {
t.Errorf("expected nil for nil user, got %v", m)
}
}
func TestLabel(t *testing.T) {
l := &gitea_sdk.Label{
ID: 1,
Name: "bug",
Color: "#d73a4a",
Description: "Something isn't working",
Exclusive: false,
}
m := Label(l)
if m["name"] != "bug" {
t.Errorf("expected name bug, got %v", m["name"])
}
if m["color"] != "#d73a4a" {
t.Errorf("expected color, got %v", m["color"])
}
}
func TestRepo(t *testing.T) {
r := &gitea_sdk.Repository{
ID: 1,
FullName: "org/repo",
Description: "A test repo",
HTMLURL: "https://gitea.com/org/repo",
CloneURL: "https://gitea.com/org/repo.git",
SSHURL: "git@gitea.com:org/repo.git",
DefaultBranch: "main",
Language: "Go",
Stars: 10,
Forks: 2,
Owner: &gitea_sdk.User{UserName: "org"},
Topics: []string{"mcp", "gitea"},
}
m := Repo(r)
if m["full_name"] != "org/repo" {
t.Errorf("expected full_name org/repo, got %v", m["full_name"])
}
if m["owner"] != "org" {
t.Errorf("expected owner org, got %v", m["owner"])
}
topics := m["topics"].([]string)
if len(topics) != 2 {
t.Errorf("expected 2 topics, got %d", len(topics))
}
}
func TestBodyWithAttachments(t *testing.T) {
atts := []*gitea_sdk.Attachment{
{Name: "shot.png", DownloadURL: "https://example/shot.png"},
{Name: "log.txt", DownloadURL: "https://example/log.txt"},
}
got := BodyWithAttachments("see attached", atts)
want := "see attached\n\n[shot.png](https://example/shot.png)\n[log.txt](https://example/log.txt)"
if got != want {
t.Errorf("got %q, want %q", got, want)
}
if got := BodyWithAttachments("only body", nil); got != "only body" {
t.Errorf("nil attachments should return body unchanged, got %q", got)
}
if got := BodyWithAttachments("", atts); got != "[shot.png](https://example/shot.png)\n[log.txt](https://example/log.txt)" {
t.Errorf("empty body should drop separator, got %q", got)
}
skipped := []*gitea_sdk.Attachment{nil, {Name: "noop", DownloadURL: ""}}
if got := BodyWithAttachments("body", skipped); got != "body" {
t.Errorf("nil/empty-URL attachments should be skipped, got %q", got)
}
}
+4 -1
View File
@@ -4,6 +4,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"gitea.com/gitea/gitea-mcp/pkg/flag"
"gitea.com/gitea/gitea-mcp/pkg/log" "gitea.com/gitea/gitea-mcp/pkg/log"
"github.com/mark3labs/mcp-go/mcp" "github.com/mark3labs/mcp-go/mcp"
@@ -14,11 +15,13 @@ func TextResult(v any) (*mcp.CallToolResult, error) {
if err != nil { if err != nil {
return nil, fmt.Errorf("marshal result err: %v", err) return nil, fmt.Errorf("marshal result err: %v", err)
} }
if flag.Debug {
log.Debugf("Text Result: %s", string(resultBytes)) log.Debugf("Text Result: %s", string(resultBytes))
}
return mcp.NewToolResultText(string(resultBytes)), nil return mcp.NewToolResultText(string(resultBytes)), nil
} }
func ErrorResult(err error) (*mcp.CallToolResult, error) { func ErrorResult(err error) (*mcp.CallToolResult, error) {
log.Errorf(err.Error()) log.Errorf("%s", err.Error())
return nil, err return nil, err
} }