forgejo-mcp/openspec/specs/release-management/spec.md
Christoph Görn 1dc7a17fb2
docs: 📝 archive add-releases-support, sync release-management spec
Implementation landed in PR #134 (commit a41115b, merge 4554d59).
Move change artifacts to openspec/changes/archive/2026-05-12-add-releases-support/
and create openspec/specs/release-management/spec.md from the delta.
2026-05-12 17:01:14 +02:00

14 KiB

release-management Specification

Purpose

The release-management capability exposes Forgejo's release and release-attachment endpoints through MCP tools, so that LLMs and CLI callers can list, read, author, edit, and delete tag-anchored releases and their binary assets without leaving the MCP surface. All list endpoints are bounded per docs/design/output-bounding.md; the download tool reuses the 1 MiB inline cap and BlobResourceContents envelope shared with the issue/comment attachment domains.

Requirements

Requirement: List releases for a repository

The system SHALL expose an MCP tool list_releases that returns a paginated list of releases for a given repository. The tool SHALL accept owner (string, required), repo (string, required), page (number, default 1), limit (number, default 20), and state (string, default all, one of all | draft | prerelease | published). The tool SHALL satisfy docs/design/output-bounding.md: client-controlled bound, no silent truncation. The state filter SHALL be applied client-side after the SDK call: draft matches releases with Draft=true; prerelease matches Draft=false && Prerelease=true; published matches Draft=false && Prerelease=false; all returns every release. The tool description SHALL note that the filter runs after pagination, so result size may be smaller than limit even when more matches exist on later pages.

Scenario: Default pagination returns first page

  • WHEN the caller invokes list_releases with only owner and repo
  • THEN the system SHALL return page 1 with up to 20 releases as a JSON array of Release objects
  • AND the system SHALL NOT cap the response with an undocumented byte limit

Scenario: Caller controls page size

  • WHEN the caller invokes list_releases with page=2 and limit=5
  • THEN the system SHALL forward those values to ListReleases via ListReleasesOptions.ListOptions{Page: 2, PageSize: 5}
  • AND the system SHALL return up to 5 releases from the second page

Scenario: Repository has no releases

  • WHEN the caller invokes list_releases against a repo with zero releases
  • THEN the system SHALL return an empty JSON array
  • AND the system SHALL NOT return an error

Scenario: State filter excludes drafts

  • WHEN the caller invokes list_releases with state=published against a repo containing a mix of drafts, prereleases, and published releases
  • THEN the system SHALL return only releases where both Draft=false and Prerelease=false

Scenario: Invalid state value

  • WHEN the caller invokes list_releases with state=foo
  • THEN the system SHALL return an MCP error result identifying the invalid state value
  • AND the system SHALL NOT call the SDK

Requirement: Fetch a release by numeric ID

The system SHALL expose an MCP tool get_release_by_id that returns a single release by its numeric ID. The tool SHALL accept owner (string, required), repo (string, required), and release_id (number, required).

Scenario: Release exists

  • WHEN the caller invokes get_release_by_id with a valid release_id
  • THEN the system SHALL return the Release object JSON-encoded

Scenario: Release not found

  • WHEN the caller invokes get_release_by_id with an unknown release_id
  • THEN the system SHALL return an MCP error result wrapping the SDK's not-found error

Requirement: Fetch a release by tag name

The system SHALL expose an MCP tool get_release_by_tag that returns a single release identified by its tag name. The tool SHALL accept owner (string, required), repo (string, required), and tag (string, required).

Scenario: Tag exists and has a release

  • WHEN the caller invokes get_release_by_tag with an existing tag that has a release attached
  • THEN the system SHALL return the Release object

Scenario: Tag has no release

  • WHEN the caller invokes get_release_by_tag with a tag that has no release attached
  • THEN the system SHALL return an MCP error result wrapping the SDK error

Requirement: Fetch the latest non-draft, non-prerelease release

The system SHALL expose an MCP tool get_latest_release that returns the most recent published release. The tool SHALL accept owner (string, required) and repo (string, required).

Scenario: Repository has published releases

  • WHEN the caller invokes get_latest_release against a repo with at least one published release
  • THEN the system SHALL return the most recent non-draft, non-prerelease Release

Scenario: Repository has only drafts or prereleases

  • WHEN the caller invokes get_latest_release against a repo whose only releases are drafts or prereleases
  • THEN the system SHALL return an MCP error result wrapping the SDK's not-found error

Requirement: Create a release

The system SHALL expose an MCP tool create_release that creates a new release. The tool SHALL accept owner (string, required), repo (string, required), tag_name (string, required), target_commitish (string, optional), name (string, optional), body (string, optional), draft (boolean, default false), and prerelease (boolean, default false). When the tag does not yet exist, target_commitish SHALL be passed to the SDK so Forgejo creates the tag.

Scenario: Create against an existing tag

  • WHEN the caller invokes create_release with tag_name referencing an existing tag and no target_commitish
  • THEN the system SHALL call CreateRelease with the provided fields and return the created Release

Scenario: Create a new tag at a specific commit

  • WHEN the caller invokes create_release with a tag_name that does not yet exist and a target_commitish set to a commit SHA or branch
  • THEN the system SHALL include target_commitish in CreateReleaseOption so Forgejo creates the tag
  • AND the system SHALL return the created Release

Scenario: Create a draft release

  • WHEN the caller invokes create_release with draft=true
  • THEN the created release SHALL have Draft=true in the returned Release object

Requirement: Edit an existing release

The system SHALL expose an MCP tool edit_release that updates fields of an existing release. The tool SHALL accept owner (string, required), repo (string, required), release_id (number, required), and any subset of tag_name, target_commitish, name, body, draft, prerelease as optional fields.

Scenario: Update release body only

  • WHEN the caller invokes edit_release with only release_id and a new body
  • THEN the system SHALL call EditRelease with an EditReleaseOption whose Note is set and other fields left zero-valued
  • AND the system SHALL return the updated Release

Scenario: Promote a draft to published

  • WHEN the caller invokes edit_release with draft=false on a release that was previously draft=true
  • THEN the returned Release SHALL have Draft=false

Requirement: Delete a release by numeric ID

The system SHALL expose an MCP tool delete_release that deletes a release by its numeric ID. The tool SHALL accept owner (string, required), repo (string, required), and release_id (number, required). The tool description SHALL warn that the operation is destructive.

Scenario: Delete succeeds

  • WHEN the caller invokes delete_release with a valid release_id
  • THEN the system SHALL call DeleteRelease and return a success result

Scenario: Release not found

  • WHEN the caller invokes delete_release with an unknown release_id
  • THEN the system SHALL return an MCP error result wrapping the SDK error

Requirement: Delete a release by tag name

The system SHALL expose an MCP tool delete_release_by_tag that deletes a release identified by its tag name. The tool SHALL accept owner (string, required), repo (string, required), and tag (string, required). The tool description SHALL warn that the operation is destructive and that callers must verify the tag.

Scenario: Delete by tag succeeds

  • WHEN the caller invokes delete_release_by_tag with an existing tag
  • THEN the system SHALL call DeleteReleaseByTag and return a success result

Scenario: Tag has no release

  • WHEN the caller invokes delete_release_by_tag with a tag that has no release
  • THEN the system SHALL return an MCP error result wrapping the SDK error

Requirement: List attachments of a release

The system SHALL expose an MCP tool list_release_attachments that returns attachments for a given release. The tool SHALL accept owner (string, required), repo (string, required), release_id (number, required), page (number, default 1), and limit (number, default 20). Because the SDK does not paginate server-side for this endpoint, the system SHALL slice the full response client-side; the tool description SHALL state this trade-off.

Scenario: Release has attachments

  • WHEN the caller invokes list_release_attachments for a release with attachments
  • THEN the system SHALL return up to limit attachments starting at offset (page-1)*limit

Scenario: Release has no attachments

  • WHEN the caller invokes list_release_attachments for a release with zero attachments
  • THEN the system SHALL return an empty JSON array

Requirement: Fetch metadata for a single release attachment

The system SHALL expose an MCP tool get_release_attachment that returns metadata (including browser_download_url) for one attachment. The tool SHALL accept owner (string, required), repo (string, required), release_id (number, required), and attachment_id (number, required).

Scenario: Attachment exists

  • WHEN the caller invokes get_release_attachment with valid IDs
  • THEN the system SHALL return the Attachment object including browser_download_url

Scenario: Attachment not found

  • WHEN the caller invokes get_release_attachment with an unknown attachment_id
  • THEN the system SHALL return an MCP error result wrapping the SDK error

Requirement: Upload an attachment to a release

The system SHALL expose an MCP tool create_release_attachment that uploads a new attachment to a release. The tool SHALL accept owner (string, required), repo (string, required), release_id (number, required), content (string, required, base64-encoded), filename (string, required), and mime_type (string, optional).

Scenario: Successful upload

  • WHEN the caller invokes create_release_attachment with valid IDs, a valid base64 content, and a filename
  • THEN the system SHALL decode content and pass an io.Reader plus filename to CreateReleaseAttachment
  • AND the system SHALL return the new Attachment

Scenario: Invalid base64 content

  • WHEN the caller invokes create_release_attachment with content that is not valid base64
  • THEN the system SHALL return an MCP error result identifying the decoding failure
  • AND the system SHALL NOT call the SDK

Requirement: Download a release attachment

The system SHALL expose an MCP tool download_release_attachment that returns either the inline bytes of an attachment (base64-encoded as an MCP embedded resource) or the metadata plus browser_download_url when the file size meets or exceeds the inline cap. The tool SHALL accept owner (string, required), repo (string, required), release_id (number, required), and attachment_id (number, required). The inline cap SHALL match the constant used by download_issue_attachment so behavior is consistent across attachment domains.

Scenario: File below inline cap

  • WHEN the caller invokes download_release_attachment for an attachment whose size is below the inline cap
  • THEN the system SHALL fetch the bytes from browser_download_url using the same Forgejo auth token and return the result with Inline=true, BytesIncluded set to the file size, and the bytes embedded as a base64 resource

Scenario: File at or above inline cap

  • WHEN the caller invokes download_release_attachment for an attachment whose size meets or exceeds the inline cap
  • THEN the system SHALL return the attachment metadata with Inline=false, Reason explaining the cap, and BytesIncluded=0
  • AND the response SHALL include browser_download_url so the caller can fetch the file directly

Scenario: Attachment not found

  • WHEN the caller invokes download_release_attachment with an unknown attachment_id
  • THEN the system SHALL return an MCP error result wrapping the SDK error

Requirement: Edit a release attachment

The system SHALL expose an MCP tool edit_release_attachment that renames an attachment. The tool SHALL accept owner (string, required), repo (string, required), release_id (number, required), attachment_id (number, required), and name (string, required).

Scenario: Rename succeeds

  • WHEN the caller invokes edit_release_attachment with a new name
  • THEN the system SHALL call EditReleaseAttachment with EditAttachmentOptions{Name: name}
  • AND the system SHALL return the updated Attachment

Requirement: Delete a release attachment

The system SHALL expose an MCP tool delete_release_attachment that removes an attachment from a release. The tool SHALL accept owner (string, required), repo (string, required), release_id (number, required), and attachment_id (number, required). The tool description SHALL warn that the operation is destructive.

Scenario: Delete succeeds

  • WHEN the caller invokes delete_release_attachment with valid IDs
  • THEN the system SHALL call DeleteReleaseAttachment and return a success result

Scenario: Attachment not found

  • WHEN the caller invokes delete_release_attachment with an unknown attachment_id
  • THEN the system SHALL return an MCP error result wrapping the SDK error