debug.ReadBuildInfo() can report a version whose major version does
not match the module path's major version suffix (e.g. a v2 tag on a
module without a /v2 path), which is unreliable per Go's module
versioning rules. Extract the decision into resolveVersion() and only
accept the build version when its major matches the module path's
implied major version, otherwise fall back to the dev version.
Fixes#231.
Co-Authored-By: Codet <codet@commitgo.dev> (GPT-5-Codex)
Release binaries are currently only attached to the Gitea release, with no mirror on the download CDN. This adds a Cloudflare R2 upload to the tag release workflow, following the approach used by [gitea/tea](https://gitea.com/gitea/tea).
- Add `scripts/upload-r2.sh`, taken from `gitea/tea`: it uploads a single file to an R2 object key with curl's built-in AWS SigV4 signer (R2 is S3-API compatible), and supports a `--check-config` preflight mode. Credentials are passed through a curl config file on stdin so they never appear in `ps` output.
- Add a `Check R2 configuration` step at the beginning of the `goreleaser` job, so a missing `R2_*` secret fails the run before anything is built or published.
- Add an `Upload binaries to Cloudflare R2` step after GoReleaser, which mirrors every archive and `checksums.txt` from `dist/` to `gitea-mcp/<version>/<file>` in the bucket. `tea` does this with a goreleaser `publishers:` entry, but that is a GoReleaser Pro feature and this repository uses the OSS distribution, so the upload runs as a workflow step instead.
Required repository secrets: `R2_ENDPOINT`, `R2_BUCKET`, `R2_ACCESS_KEY_ID`, `R2_SECRET_ACCESS_KEY`. No AWS S3 upload is added; Cloudflare R2 is the only mirror.
Tested locally by pointing `R2_ENDPOINT` at a local HTTP server and verifying the script builds the expected path-style URL and signed request, plus `shellcheck` on the script.
_Authored by Codet (GPT-5-Codex) on behalf of @lunny._
---------
Co-authored-by: bircni <bircni@icloud.com>
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/237
Reviewed-by: silverwind <2021+silverwind@noreply.gitea.com>
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Adds MCP protocol `2026-07-28` over HTTP through the official Go SDK, and validates the `Origin` header on every request as the spec requires.
Tool and Gitea failures now come back as an ordinary `tools/call` result carrying `result.isError: true`, the way the SDK's own tool wrapper reports them. Malformed requests, unknown tools or methods, and server faults stay JSON-RPC errors.
Adds `-b, --bind` to narrow the listen address. The default still accepts every interface, so this is opt-in hardening. It matters because a request that omits `Authorization` falls back to the server's own token.
**Breaking: the HTTP endpoint no longer keeps a session per client.** What changes for a client:
1. `/mcp` accepts `POST` only, and answers `405` to `GET` or `DELETE`.
2. The server neither sends nor accepts `Mcp-Session-Id`, so there is no session handshake to perform.
3. There is no standalone SSE stream and no `Last-Event-ID` resumption. If a response stream breaks, send the whole request again under a new JSON-RPC id.
Clients that already speak current streamable HTTP need no changes. Anything relying on the session handshake or the standalone SSE stream should stay on the previous release.
---------
Co-authored-by: silverwind <me@silverwind.io>
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/227
Reviewed-by: silverwind <2021+silverwind@noreply.gitea.com>
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
## Summary
Replace `github.com/mark3labs/mcp-go` with the official
`github.com/modelcontextprotocol/go-sdk v1.7.0`.
All 54 existing tools, scopes, CLI, `stdio` and HTTP (`/mcp`) modes, and
per-request authentication remain supported. This PR is limited to the SDK
equivalence migration; the stateless 2026 HTTP transport is deferred to a
follow-up PR.
## Verification
- Tool definitions were compared with `main` and matched for all 54 tools.
- `make lint`, `make fmt`, `go test -count=1 -race ./...`, `make build`, and
`make tidy` pass.
---------
Co-authored-by: bircni <bircni@icloud.com>
Co-authored-by: silverwind <me@silverwind.io>
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/223
Reviewed-by: silverwind <2021+silverwind@noreply.gitea.com>
Reviewed-by: bircni <bircni@icloud.com>
Co-authored-by: Bo-Yi Wu <appleboy.tw@gmail.com>
`-O` / `--tools` can already narrow the exposed tool set, but it needs every tool spelled out by name. `-S` / `--scope` (`GITEA_SCOPES`) complements it by selecting whole scopes, using the same names the `Scope` column of the tool tables documents.
```bash
gitea-mcp -S issue,pull_request # only those scopes
gitea-mcp --scope repository,branch --tools get_me # those scopes plus one extra tool
```
## What changed
- Each scope is one `tool.Tool` registry carrying a canonical name, so `operation/repo` is split into the six scopes its files already imply: `repository` (`repo.go` + `tree.go`), `file`, `branch`, `tag`, `commit`, `release`. The other twelve packages map 1:1, giving 18 scopes.
- The two allowlists combine as a **union**: with neither set every tool loads; with only `--tools` set behaviour is unchanged; with both set, the selected scopes' tools plus the individually named tools load. `-r` / `GITEA_READONLY` still hides write tools on top.
- Scope names are normalized on input (case, spaces, hyphens), so `Pull Request`, `pull-request` and `PULL_REQUEST` all resolve to `pull_request`. Unknown names only warn and list the valid scopes, mirroring how `--tools` treats unknown tool names.
- The `Scope` column of all three READMEs now uses the canonical names verbatim, so it doubles as the reference for `--scope`, and `TestReadmeToolTables` compares that column against the registry in both directions — no translation table needed.
- Flag parsing moves from `cmd.init()` into `Execute()`. `main()` only ever calls `Execute()`, so this is behaviourally equivalent, and it makes the `cmd` package testable at all: previously the `init()` parse of `os.Args` hit `flag.CommandLine`'s `ExitOnError` on `go test`'s own `-test.*` flags.
## Verification
`make fmt`, `make lint-go` (0 issues) and `go test ./...` all pass. New tests cover the filter matrix (no filters / scope-only / tools-only regression / union / read-only interaction / unknown scope), scope-name uniqueness across `domainTools`, and the flag+env parsing and normalization.
Smoke-tested `tools/list` over stdio against the built binary:
| flags | tools exposed |
| :-- | :-- |
| _none_ | 54 (identical to `main`) |
| `-S branch` | `create_branch`, `delete_branch`, `list_branches` |
| `--scope 'Pull Request,TAG'` | the 4 `pull_request` + 4 `tag` tools |
| `-O get_me` | `get_me` (unchanged) |
| `-S commit -O get_me` | `get_commit`, `list_commits`, `get_me` |
| `-S file -r` | `get_dir_contents`, `get_file_contents` |
| `-S bogus,issue` | the 4 `issue` tools, plus a warning naming the valid scopes |
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/219
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: yp05327 <576951401@qq.com>
- Add a read-only `attachment_read` tool for issue and issue comment attachments.
- Use the existing Gitea SDK attachment metadata APIs for `list` and `get`.
- Add authenticated `/attachments/{uuid}` downloads in the MCP server for `download`, with small image inline results and streamed file downloads for larger content.
- Add `-max-inline-attachment-bytes` / `GITEA_MAX_INLINE_ATTACHMENT_BYTES` to configure inline image limits.
- Update the tool tables in all README variants.
Refs: #209
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/218
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: yp05327 <576951401@qq.com>
Fixes https://gitea.com/gitea/gitea-mcp/issues/214.
The tool tables listed tools that had not existed for several releases. They are regenerated from the registry, gain an `Access` column so it is visible what disappears under `-r` / `GITEA_READONLY`, and get their fine-grained scopes back. `TestReadmeToolTables` now compares them against the registry in both directions, for every translation.
Other corrections from verifying the prose against the source: the pagination parameter is `per_page`, not `perPage`; Go 1.26 is required, not 1.24; the Chinese READMEs were missing the OpenCode and Mistral Vibe sections.
Also trimmed the drifted table of contents and the sections the intro and `gitea-mcp --help` already cover, and dropped the heading emoji. `CLAUDE.md` had its own stale tool list, so it now points at `AGENTS.md` like gitea/gitea does.
---------
Co-authored-by: silverwind <me@silverwind.io>
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/215
Reviewed-by: silverwind <2021+silverwind@noreply.gitea.com>
Adds a read-only `get_job` method to the `actions_run_read` tool. `actions_run_read` could already `list_jobs` / `list_run_jobs` (slimmed lists) and read job logs by `job_id`, but there was no way to fetch the full detail of one job. `get_job` closes that gap.Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/211
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: bircni <bircni@icloud.com>
Bumps all direct dependencies to their latest versions.
| Dependency | From | To |
| ----------------------------- | ------- | ------------ |
| `gitea.dev/sdk` | v1.0.1 | v1.2.0 |
| `github.com/mark3labs/mcp-go` | v0.45.0 | v0.56.0 |
| `go.uber.org/zap` | v1.27.1 | v1.28.0 |
| `go.uber.org/zap/exp` | — | v0.3.0 (new) |
### Required code change
mcp-go v0.56 moved transport logging to `log/slog` and renamed the streamable-HTTP logger option to `WithStreamableHTTPLogger(*slog.Logger)`. Added a `log.Slog()` bridge (via the official `zapslog` adapter) so the HTTP transport keeps logging through the existing zap pipeline, and switched `operation.go` to the new option.
---------
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/212
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: bircni <bircni@icloud.com>
Adds the ability to list and download Gitea Actions artifacts. Extends the existing `actions_run_read` tool with four new methods rather than adding a separate tool, keeping artifacts alongside the runs/jobs/logs they belong to.
### New methods (`actions_run_read`)
| Method | Description | Key params |
| -------------------- | ------------------------------------------ | ------------------------------------------------------ |
| `list_artifacts` | List all artifacts in a repository | `owner`, `repo`, optional `artifact_name` filter |
| `list_run_artifacts` | List artifacts for one workflow run | `owner`, `repo`, `run_id` |
| `get_artifact` | Get metadata for a single artifact | `owner`, `repo`, `artifact_id` |
| `download_artifact` | Download an artifact's zip archive to disk | `owner`, `repo`, `artifact_id`, optional `output_path` |Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/210
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: bircni <bircni@icloud.com>
## 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>
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>
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>
- Tool list size reduced by 26.6% (43,032 → 31,599 bytes on the `tools/list` JSON-RPC response).
- Trim redundant tool/param descriptions; shared description constants for `owner`/`repo`/`page`/`per_page`.
- Schemas now use github-mcp-server param names directly: `issue_number` (was `index` on issue tools), `pull_number` (was `index` on PR tools), `path` (was `filePath`), `query` (was `keyword` on user/repo search), `per_page` (was `perPage`).
- New PR read methods `get_files` and `get_status`; new PR write method `update_branch` (update PR branch from base).
- `list_org_repos` now uses `per_page` (was `pageSize`).
- `milestone_write` accepts `update` and `edit`.
- `create_branch` `old_branch` is optional; Gitea defaults to the repo default branch.
- Fix `list_commits` handler to honour optional `page`/`per_page` schema (was erroring out when callers omitted them).
---
This PR was written with the help of Claude Opus 4.7
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/191
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-committed-by: silverwind <me@silverwind.io>
Fixes https://gitea.com/gitea/gitea-mcp/issues/187
The `due_on` argument was declared in the `milestone_write` schema but never read by `createMilestoneFn` or `editMilestoneFn`, so `opt.Deadline` was always nil and the field was silently dropped. This reuses the existing `params.GetOptionalTime` helper that already handles the analogous `deadline` field on issues and pull requests.
---
This PR was written with the help of Claude Opus 4.7
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/189
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-committed-by: silverwind <me@silverwind.io>
Bump `golangci-lint` to v2.12.2 and pin `govulncheck` to v1.3.0. Align `.golangci.yml` with the gitea repo's config: enable `revive` `var-naming` (with `skip-package-name-checks`) and drop the test-file exclusion for `errcheck`/`staticcheck`/`unparam`. Fix the now-surfaced `errcheck` violations in test handlers by discarding return values to match the existing codebase pattern.
---
This PR was written with the help of Claude Opus 4.7
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/190
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-committed-by: silverwind <me@silverwind.io>
Adds `-O`/`-tools` CLI flag and `GITEA_TOOLS` environment variable
accepting a comma-separated list of tool names. When set, only the
listed tools are exposed to MCP clients, which lets AI agents trim
their tool context. Composes with `--read-only`. Unknown names are
logged at startup so typos surface instead of failing silently.
Co-Authored-By: silverwind <me@silverwind.io>
Co-Authored-By: Claude (Opus 4.7) <noreply@anthropic.com>
Adds `package_read` and `package_write` MCP tools for the Gitea
Packages API.
- `package_read` (read): `list`, `list_versions`, `get`
- `package_write` (write): `delete`
Package names containing slashes (e.g. container image paths like
`my-repo/my-image`) are accepted raw or pre-encoded and URL-encoded
correctly without double-encoding.
Co-Authored-By: silverwind <me@silverwind.io>
Co-Authored-By: Claude (Opus 4.7) <noreply@anthropic.com>
Add MCP `ToolAnnotation` metadata (Title, ReadOnlyHint, DestructiveHint)
to all registered tools so MCP hosts (VS Code, Claude, Cursor) get
accurate per-tool hints. A shared `pkg/annotation` package exposes
`ReadOnly`, `Write`, and `Destructive` helpers for consistency.
Add `close` and `reopen` methods to `pull_request_write` so PR state
can be toggled without going through the generic `update` path.
Co-Authored-By: silverwind <me@silverwind.io>
Co-Authored-By: Claude (Opus 4.7) <noreply@anthropic.com>
I don't like secrets just being added via environment variables. Add support for the `_FILE` environment variable convention used by Docker secrets.
When `GITEA_ACCESS_TOKEN_FILE` is set, the token is read from the file at that path (e.g. `/run/secrets/gitea_token`). Trailing newlines are stripped to handle the typical Docker secrets file format on both Linux and Windows.
Token resolution precedence (highest to lowest):
1. `--token` / `-T` CLI flag
2. `GITEA_ACCESS_TOKEN` env var
3. `GITEA_ACCESS_TOKEN_FILE` env var
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/186
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: Dennis Gaida <gitea@mail.gaida.biz>
Co-committed-by: Dennis Gaida <gitea@mail.gaida.biz>
The Gitea API returns an `assets` array on issue and comment responses, but the SDK structs drop it — so attachments are invisible to MCP agents.
Append each attachment as a `[name](url)` markdown link at the end of the body, mirroring how GitHub embeds attachments inline (which `github-mcp-server` preserves as-is).
**Coverage:**
- `issue_read get` — issue body attachments
- `issue_read get_comments` — issue and PR conversation comment attachments (same endpoint)
- `pull_request_read get` — PR description attachments (Gitea's `/pulls/` endpoint omits `assets`, so a follow-up best-effort call to `/issues/{n}/assets` surfaces them; PRs are issues internally)
PR review summaries and line-comment reviews don't support attachments per the Gitea API spec, so nothing to do there.
Closes https://gitea.com/gitea/gitea-mcp/issues/182
---
This PR was written with the help of Claude Opus 4.7
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/183
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-committed-by: silverwind <me@silverwind.io>
Add notification management via two new MCP tools using the method-dispatch pattern:
- `notification_read`: list notifications (global or repo-scoped, with status/subject_type/since/before filters) and get single notification thread by ID
- `notification_write`: mark single notification as read, mark all notifications as read (global or repo-scoped)
---
This PR was written with the help of Claude Opus 4.6
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/172
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-committed-by: silverwind <me@silverwind.io>
## Summary
### Read (issue responses)
- Add `ref` (branch) field to issue responses when non-empty
- Add `deadline` (due_date) field to issue responses when non-nil
- Applied to `slimIssue()`, `slimIssues()` in `operation/issue/slim.go` and `slimIssues()` in `operation/search/slim.go`
### Write (issue_write)
- Add `ref` parameter to `issue_write` tool for both `create` and `update` methods
- Allows setting the branch reference on issues via the MCP, consistent with the SDK's `CreateIssueOption.Ref` and `EditIssueOption.Ref` fields
Fixes#168
## Test plan
- [ ] `go test ./...` passes
- [ ] Verify `issue_read` returns `ref` when a branch is assigned to an issue
- [ ] Verify `issue_read` returns `deadline` when a due date is set
- [ ] Verify `list_issues` and `search_issues` include these fields
- [ ] Verify `issue_write` with method `update` can set `ref` on an issue
- [ ] Verify `issue_write` with method `create` can set `ref` on a new issue
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/169
Reviewed-by: silverwind <2021+silverwind@noreply.gitea.com>
Co-authored-by: pengu <jeremy@wenjy.fr>
Co-committed-by: pengu <jeremy@wenjy.fr>
Expose additional parameters that the Gitea SDK supports but were not yet wired through the MCP tool definitions.
**Motivation:** Systematic comparison against github-mcp-server and the Gitea SDK revealed several supported parameters that were missing from tool schemas.
- `list_issues`: `labels`, `since`, `before` filters
- `issue_write`: `labels` and `deadline` on create, `deadline`/`remove_deadline` on update
- `pull_request_write`: `labels`/`deadline` on create/update, `remove_deadline` on update, `force_merge`/`merge_when_checks_succeed`/`head_commit_id` on merge
- `list_branches`: `page`/`perPage` pagination
- `create_repo`: `trust_model`, `object_format_name`
- `label_write`: `is_archived` on create/edit
**Testing:** Added tests for issue list filters, issue create labels/deadline, PR create labels/deadline, and PR merge new params.
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/164
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-committed-by: silverwind <me@silverwind.io>
Add three new read-only tools inspired by the GitHub MCP server:
- `get_commit`: Get details of a specific commit by SHA, branch, or tag
- `get_repository_tree`: Get the file tree of a repository with optional recursive traversal, pagination, and ref support
- `search_issues`: Search issues and pull requests across all accessible repositories with filters for state, type, labels, and owner
---------
Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/162
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-committed-by: silverwind <me@silverwind.io>
## Summary
The Gitea API has no native `draft` field on `CreatePullRequestOption` or `EditPullRequestOption`. Instead, Gitea treats PRs whose title starts with a WIP prefix (e.g. `WIP:`, `[WIP]`) as drafts. This adds a `draft` boolean parameter to the `pull_request_write` tool so MCP clients can create/update draft PRs without knowing about the WIP prefix convention.
## Changes
- Add `draft` boolean parameter to `PullRequestWriteTool` schema, supported on `create` and `update` methods
- Add `applyDraftPrefix()` helper that handles both default Gitea WIP prefixes (`WIP:`, `[WIP]`) case-insensitively
- When `draft=true` and no prefix exists, prepend `WIP: `; when a prefix already exists, preserve the title as-is (no normalization)
- When `draft=false`, strip any recognized WIP prefix
- On `update`, if `draft` is set without `title`, auto-fetch the current PR title via GET
- Add tests: 12 unit tests for `applyDraftPrefix`, 5 integration tests for create, 4 for edit
---------
Co-authored-by: tomholford <tomholford@users.noreply.github.com>
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/159
Reviewed-by: silverwind <2021+silverwind@noreply.gitea.com>
Co-authored-by: tomholford <128995+tomholford@noreply.gitea.com>
Co-committed-by: tomholford <128995+tomholford@noreply.gitea.com>
The `wiki_write` tool exposed the Gitea API's `content_base64` parameter directly, requiring callers to provide base64-encoded content. This caused LLM agents to incorrectly infer that other tools like `create_or_update_file` also require base64 encoding, corrupting files with literal base64 strings.
Rename the parameter to `content` (plain text) and handle base64 encoding internally, matching the pattern already used by `create_or_update_file`. Also added test coverage for this.
Closes#151
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/156
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-committed-by: silverwind <me@silverwind.io>
*Written by Claude on behalf of @silverwind*
When installed via `go run module@version` or `go install module@version`, the Go toolchain embeds the module version in the binary. Use `debug.ReadBuildInfo` to read it as a fallback when `Version` has not been set via ldflags, so that `go run gitea.com/gitea/gitea-mcp@latest` will reports the latest tag version instead of `dev`.
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/155
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-committed-by: silverwind <me@silverwind.io>
When a Gitea repo is renamed, the API returns a 301 redirect. Go's default `http.Client` follows 301/302/303 redirects by changing the HTTP method from PATCH/POST/PUT to GET and dropping the request body. This causes mutating API calls (edit PR, create issue, etc.) to silently appear to succeed while no write actually occurs — the client receives the current resource data via the redirected GET and returns it as if the edit worked.
## Fix
Add a `CheckRedirect` function to both HTTP clients (SDK client in `gitea.go` and REST client in `rest.go`) that returns `http.ErrUseLastResponse` for non-GET/HEAD methods. This surfaces the redirect as an error instead of silently downgrading the request. GET/HEAD reads continue to follow redirects normally.
## Tests
- `TestCheckRedirect`: table-driven unit tests for all HTTP methods + redirect limit
- `TestDoJSON_RepoRenameRedirect`: regression test with `httptest` server proving PATCH to a 301 endpoint returns an error instead of silently succeeding
- `TestDoJSON_GETRedirectFollowed`: verifies GET reads still follow 301 redirects
*This PR was authored by Claude.*
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/154
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Reverts the Content-Type middleware and custom server/mux plumbing from #149, keeping only the `http.ErrServerClosed` fix which is a legitimate bug.
The middleware is unnecessary because rmcp's `post_message` already returns early for 202/204 before any Content-Type validation. Both Go MCP SDKs (`modelcontextprotocol/go-sdk` used by GitHub's MCP server, and `mark3labs/mcp-go` used here) intentionally omit Content-Type on 202 responses per the MCP spec. See #148 for the full analysis.
*PR written by Claude on behalf of @silverwind*
Reviewed-on: https://gitea.com/gitea/gitea-mcp/pulls/150
Reviewed-by: Lunny Xiao <xiaolunwen@gmail.com>
Co-authored-by: silverwind <me@silverwind.io>
Co-committed-by: silverwind <me@silverwind.io>