feat: add 14 MCP tools for Forgejo releases and release attachments #134

Merged
goern merged 0 commits from refs/pull/134/head into main 2026-05-12 14:55:39 +00:00
goern commented 2026-05-12 14:47:41 +00:00 (Migrated from codeberg.org)

Closes #127

What

Adds operation/release/ — a new MCP tool domain covering the full Forgejo release surface:

Group Tools
Release read list_releases, get_release_by_id, get_release_by_tag, get_latest_release
Release write create_release, edit_release, delete_release, delete_release_by_tag
Attachment read list_release_attachments, get_release_attachment, download_release_attachment
Attachment write create_release_attachment, edit_release_attachment, delete_release_attachment

Key design decisions

  • SDK-native: all tools use forgejo.Client() directly — no raw HTTP fallback needed (unlike operation/attachment/, where the SDK gap forced raw HTTP).
  • list_releases state filter (all|draft|prerelease|published): applied client-side after the SDK call. Forgejo's API has no state query param; the tool description documents that result size may be smaller than limit on any given page.
  • list_release_attachments pagination: the SDK returns a full slice; the tool slices client-side [offset:offset+limit]. Trade-off documented in the tool description.
  • download_release_attachment: reuses forgejo.MaxInlineDownloadBytes (1 MiB) and the BlobResourceContents inline shape from operation/attachment/, so the over/under-cap contract is identical across all attachment domains.
  • edit_release partial update: EditReleaseOption.IsDraft/IsPrerelease are *bool — omitting them marshals to JSON null, leaving Forgejo's existing values intact. Only fields the caller supplies are sent.
  • create_release name default: the SDK's CreateReleaseOption.Validate() rejects an empty Title. If the caller omits name, the tool defaults it to tag_name so callers can omit name without tripping the validator.

Tests

19 unit tests in operation/release/release_test.go using the httptest+SetClientForTesting pattern from operation/issue/:

  • Default + custom pagination forwarded via query string
  • state=published filter excludes drafts and prereleases
  • Invalid state rejected before SDK call
  • get_release_by_id not-found path
  • create_release with target_commitish in PATCH body
  • edit_release partial update — draft not sent when caller omits it
  • delete_release + delete_release_by_tag success
  • create_release_attachment base64 decode guard (SDK not called)
  • create_release_attachment multipart round-trip
  • list_release_attachments page boundary + empty page
  • download_release_attachment under-cap inline, at-cap metadata-only, unknown-id error

Output bounding checklist (docs/design/output-bounding.md)

  • list_releases: client-controlled page + limit → SDK ListOptions; state filter documented as post-pagination
  • list_release_attachments: client-controlled page + limit; full-fetch + client-side slice documented in tool description
  • All other tools: single-item or no data — not data-proportional

Verification

  • make build
  • make vendor no-op ✓
  • openspec validate add-releases-support
  • go test ./... all green ✓
  • ./forgejo-mcp --cli list shows RELEASE: group with all 14 tools ✓
  • Read-only smoke against codeberg.org/goern/forgejo-mcp: list_releasesget_latest_releaseget_release_by_taglist_release_attachmentsdownload_release_attachment (over-cap path) ✓

Documentation

  • README.md: Releases row group added to tools table (14 tools)
  • DEVELOPER.md: operation/release/ added to domain directory table
  • demos/release-management.md: full showboat demo with real Codeberg output for the read path + write-tool parameter surface + autonomous release-notes workflow
  • demos/README.md: new §3 Releases section; sections renumbered 4–8
Closes #127 ## What Adds `operation/release/` — a new MCP tool domain covering the full Forgejo release surface: | Group | Tools | |-------|-------| | Release read | `list_releases`, `get_release_by_id`, `get_release_by_tag`, `get_latest_release` | | Release write | `create_release`, `edit_release`, `delete_release`, `delete_release_by_tag` | | Attachment read | `list_release_attachments`, `get_release_attachment`, `download_release_attachment` | | Attachment write | `create_release_attachment`, `edit_release_attachment`, `delete_release_attachment` | ## Key design decisions - **SDK-native**: all tools use `forgejo.Client()` directly — no raw HTTP fallback needed (unlike `operation/attachment/`, where the SDK gap forced raw HTTP). - **`list_releases` state filter** (`all`|`draft`|`prerelease`|`published`): applied client-side after the SDK call. Forgejo's API has no `state` query param; the tool description documents that result size may be smaller than `limit` on any given page. - **`list_release_attachments` pagination**: the SDK returns a full slice; the tool slices client-side `[offset:offset+limit]`. Trade-off documented in the tool description. - **`download_release_attachment`**: reuses `forgejo.MaxInlineDownloadBytes` (1 MiB) and the `BlobResourceContents` inline shape from `operation/attachment/`, so the over/under-cap contract is identical across all attachment domains. - **`edit_release` partial update**: `EditReleaseOption.IsDraft`/`IsPrerelease` are `*bool` — omitting them marshals to JSON null, leaving Forgejo's existing values intact. Only fields the caller supplies are sent. - **`create_release` name default**: the SDK's `CreateReleaseOption.Validate()` rejects an empty `Title`. If the caller omits `name`, the tool defaults it to `tag_name` so callers can omit name without tripping the validator. ## Tests 19 unit tests in `operation/release/release_test.go` using the `httptest`+`SetClientForTesting` pattern from `operation/issue/`: - Default + custom pagination forwarded via query string - `state=published` filter excludes drafts and prereleases - Invalid state rejected before SDK call - `get_release_by_id` not-found path - `create_release` with `target_commitish` in PATCH body - `edit_release` partial update — `draft` not sent when caller omits it - `delete_release` + `delete_release_by_tag` success - `create_release_attachment` base64 decode guard (SDK not called) - `create_release_attachment` multipart round-trip - `list_release_attachments` page boundary + empty page - `download_release_attachment` under-cap inline, at-cap metadata-only, unknown-id error ## Output bounding checklist (docs/design/output-bounding.md) - [x] `list_releases`: client-controlled `page` + `limit` → SDK `ListOptions`; state filter documented as post-pagination - [x] `list_release_attachments`: client-controlled `page` + `limit`; full-fetch + client-side slice documented in tool description - [x] All other tools: single-item or no data — not data-proportional ## Verification - `make build` ✓ - `make vendor` no-op ✓ - `openspec validate add-releases-support` ✓ - `go test ./...` all green ✓ - `./forgejo-mcp --cli list` shows `RELEASE:` group with all 14 tools ✓ - Read-only smoke against `codeberg.org/goern/forgejo-mcp`: `list_releases` → `get_latest_release` → `get_release_by_tag` → `list_release_attachments` → `download_release_attachment` (over-cap path) ✓ ## Documentation - `README.md`: Releases row group added to tools table (14 tools) - `DEVELOPER.md`: `operation/release/` added to domain directory table - `demos/release-management.md`: full showboat demo with real Codeberg output for the read path + write-tool parameter surface + autonomous release-notes workflow - `demos/README.md`: new §3 Releases section; sections renumbered 4–8
op1st-gitops commented 2026-05-12 14:52:03 +00:00 (Migrated from codeberg.org)

op1st Pipelines as Code/code-scans-ftzml is running.

Starting Pipelinerun code-scans-ftzml in namespace op1st-pipelines

You can monitor the execution using the op1st Pipelines as Code PipelineRun viewer or through the command line by
using the tkn CLI with the following command:

tkn pr logs -n op1st-pipelines code-scans-ftzml -f

op1st Pipelines as Code/code-scans-ftzml is running. Starting Pipelinerun <b>[code-scans-ftzml](https://console-openshift-console.apps.nostromo.erdgeschoss.b4mad.emea.operate-first.cloud/k8s/ns/op1st-pipelines/tekton.dev~v1~PipelineRun/code-scans-ftzml)</b> in namespace <b>op1st-pipelines</b> You can monitor the execution using the [op1st Pipelines as Code](https://console-openshift-console.apps.nostromo.erdgeschoss.b4mad.emea.operate-first.cloud/k8s/ns/op1st-pipelines/tekton.dev~v1~PipelineRun/code-scans-ftzml) PipelineRun viewer or through the command line by using the [tkn](https://tekton.dev/docs/cli/#installation) CLI with the following command: <code>tkn pr logs -n op1st-pipelines code-scans-ftzml -f</code>
op1st-gitops commented 2026-05-12 14:52:03 +00:00 (Migrated from codeberg.org)

op1st Pipelines as Code/openspec-validate-pr-wcw49 is running.

Starting Pipelinerun openspec-validate-pr-wcw49 in namespace op1st-pipelines

You can monitor the execution using the op1st Pipelines as Code PipelineRun viewer or through the command line by
using the tkn CLI with the following command:

tkn pr logs -n op1st-pipelines openspec-validate-pr-wcw49 -f

op1st Pipelines as Code/openspec-validate-pr-wcw49 is running. Starting Pipelinerun <b>[openspec-validate-pr-wcw49](https://console-openshift-console.apps.nostromo.erdgeschoss.b4mad.emea.operate-first.cloud/k8s/ns/op1st-pipelines/tekton.dev~v1~PipelineRun/openspec-validate-pr-wcw49)</b> in namespace <b>op1st-pipelines</b> You can monitor the execution using the [op1st Pipelines as Code](https://console-openshift-console.apps.nostromo.erdgeschoss.b4mad.emea.operate-first.cloud/k8s/ns/op1st-pipelines/tekton.dev~v1~PipelineRun/openspec-validate-pr-wcw49) PipelineRun viewer or through the command line by using the [tkn](https://tekton.dev/docs/cli/#installation) CLI with the following command: <code>tkn pr logs -n op1st-pipelines openspec-validate-pr-wcw49 -f</code>
op1st-gitops commented 2026-05-12 14:52:03 +00:00 (Migrated from codeberg.org)

op1st Pipelines as Code/on-pull-request- has failed.
There was an error creating the PipelineRun: on-pull-request-

cannot use the API on the provider platform to create a in_progress status:

op1st Pipelines as Code/on-pull-request- has <b>failed</b>. There was an error creating the PipelineRun: <b>on-pull-request-</b> cannot use the API on the provider platform to create a in_progress status:
op1st-gitops commented 2026-05-12 14:52:25 +00:00 (Migrated from codeberg.org)

op1st Pipelines as Code/code-scans-ftzml has successfully validated your commit.


Task Statuses:

StatusDurationName
Succeeded 13 seconds

fetch-source

Succeeded 8 seconds

gitleaks-version

Succeeded 9 seconds

gitleaks

op1st Pipelines as Code/code-scans-ftzml has <b>successfully</b> validated your commit. <ul> <li><b>Namespace</b>: <a href="https://detailurl.setting.custom-console-url-namespace.is.not.configured">op1st-pipelines</a></li> <li><b>PipelineRun:</b> <a href="https://console-openshift-console.apps.nostromo.erdgeschoss.b4mad.emea.operate-first.cloud/k8s/ns/op1st-pipelines/tekton.dev~v1~PipelineRun/code-scans-ftzml">code-scans-ftzml</a></li> </ul> <hr> <h4>Task Statuses:</h4> <table> <tr><th>Status</th><th>Duration</th><th>Name</th></tr> <tr> <td>Succeeded</td> <td>13 seconds</td><td> [fetch-source](https://console-openshift-console.apps.nostromo.erdgeschoss.b4mad.emea.operate-first.cloud/k8s/ns/op1st-pipelines/tekton.dev~v1~PipelineRun/code-scans-ftzml/logs/fetch-source) </td></tr> <tr> <td>Succeeded</td> <td>8 seconds</td><td> [gitleaks-version](https://console-openshift-console.apps.nostromo.erdgeschoss.b4mad.emea.operate-first.cloud/k8s/ns/op1st-pipelines/tekton.dev~v1~PipelineRun/code-scans-ftzml/logs/gitleaks-version) </td></tr> <tr> <td>Succeeded</td> <td>9 seconds</td><td> [gitleaks](https://console-openshift-console.apps.nostromo.erdgeschoss.b4mad.emea.operate-first.cloud/k8s/ns/op1st-pipelines/tekton.dev~v1~PipelineRun/code-scans-ftzml/logs/gitleaks) </td></tr> </table>
op1st-gitops commented 2026-05-12 14:52:35 +00:00 (Migrated from codeberg.org)

op1st Pipelines as Code/openspec-validate-pr-wcw49 has successfully validated your commit.


Task Statuses:

StatusDurationName
Succeeded 12 seconds

fetch-source

Succeeded 16 seconds

validate

op1st Pipelines as Code/openspec-validate-pr-wcw49 has <b>successfully</b> validated your commit. <ul> <li><b>Namespace</b>: <a href="https://detailurl.setting.custom-console-url-namespace.is.not.configured">op1st-pipelines</a></li> <li><b>PipelineRun:</b> <a href="https://console-openshift-console.apps.nostromo.erdgeschoss.b4mad.emea.operate-first.cloud/k8s/ns/op1st-pipelines/tekton.dev~v1~PipelineRun/openspec-validate-pr-wcw49">openspec-validate-pr-wcw49</a></li> </ul> <hr> <h4>Task Statuses:</h4> <table> <tr><th>Status</th><th>Duration</th><th>Name</th></tr> <tr> <td>Succeeded</td> <td>12 seconds</td><td> [fetch-source](https://console-openshift-console.apps.nostromo.erdgeschoss.b4mad.emea.operate-first.cloud/k8s/ns/op1st-pipelines/tekton.dev~v1~PipelineRun/openspec-validate-pr-wcw49/logs/fetch-source) </td></tr> <tr> <td>Succeeded</td> <td>16 seconds</td><td> [validate](https://console-openshift-console.apps.nostromo.erdgeschoss.b4mad.emea.operate-first.cloud/k8s/ns/op1st-pipelines/tekton.dev~v1~PipelineRun/openspec-validate-pr-wcw49/logs/validate) </td></tr> </table>
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
agentic-forges/forgejo-mcp!134
No description provided.