feat: label CRUD tools + label resource-templates #190
Labels
No labels
Compat/Breaking
Kind/Bug
Kind/Documentation
Kind/Enhancement
Kind/Feature
Kind/OpenSpec
Kind/Security
Kind/Testing
Priority/Critical
Priority/High
Priority/Low
Priority/Medium
RFC - Request For Comments
Reviewed/Confirmed
Reviewed/Duplicate
Reviewed/Invalid
Reviewed/Won't Fix
Status/Abandoned
Status/Blocked
Status/Need More Info
hermes-attempted
hermes-needs-clarification
hermes-ready
hermes-review
hermes-wip
human-required
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
agentic-forges/forgejo-mcp#190
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
forgejo-mcpexposes read/assignment label tools —list_repo_labels,list_org_labels,add_issue_labels,remove_issue_labels(all inoperation/issue/issue.go) — but has no label CRUD: you cannot create, edit, or delete a repo or org label through MCP.Surfaced 2026-05-31 while creating the
RFC - Request For Commentslabel for #176: the work fell back to rawcurlagainst the Forgejo REST API because no MCP tool exists. Under the project's MCP-only policy that is a hard stop, not a minor annoyance.This issue also extends the
forgejo://resource-template surface (seemcp-resources-core) to labels, so labels become URI-addressable like the existing issue / pr / commit resources.Forgejo / Gitea API surface to wrap
Repo labels (Gitea-compatible swagger):
GET/repos/{owner}/{repo}/labelsGET/repos/{owner}/{repo}/labels/{id}POST/repos/{owner}/{repo}/labelsPATCH/repos/{owner}/{repo}/labels/{id}DELETE/repos/{owner}/{repo}/labels/{id}Org labels share the shape under
/orgs/{org}/labels[/{id}].CreateLabelOption/EditLabelOptionfields (relevant):SDK vs raw-HTTP
codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v3(the pinned SDK) already provides repo-label CRUD — use these directly:CreateLabel(owner, repo, CreateLabelOption) (*Label, …)EditLabel(owner, repo, id, EditLabelOption) (*Label, …)DeleteLabel(owner, repo, id) (…)GetRepoLabel(owner, repo, id) (*Label, …)/ListRepoLabels(...)The SDK
Labelstruct exposesid,name,color,description,urlonly. It does not model Forgejo's newerexclusive/is_archivedlabel fields — those are out of scope for the first cut (note it explicitly; do not silently drop them — a follow-up can add raw-HTTP passthrough if needed).Org-label CRUD has no SDK method. Org labels must go through the
pkg/forgejoraw-HTTP helper (DoJSON), exactly like the existingfetchOrgLabelsinoperation/issue/issue.goand the wiki/attachment precedent.Proposed MCP tools
Naming consistent with existing
create_*/edit_*/delete_*patterns:create_label(owner, repo, name, color, description?)→ returns the created label (incl. numericid).edit_label(owner, repo, id, name?, color?, description?)→ updated label (PATCH semantics: only provided fields change).delete_label(owner, repo, id)→ success.get_repo_label(owner, repo, id)→ single label (fills the read-one gap;list_repo_labelsalready lists).Org-label CRUD (
create_org_label/edit_org_label/delete_org_label, raw-HTTP) is a secondary scope — land repo-label CRUD first, org follows the same shape viaDoJSON.coloraccepts the Forgejo hex form (#rrggbb); validate/normalise before send so a missing#doesn't 422.Proposed resource-templates (additive surface)
Per
AGENTS.md/openspec/specs/mcp-resources-core, labels should ALSO be URI-addressable — additive, instance-portable, coexisting with the tools above (no tool removed). Singularreposegment, matching the establishedforgejo://repo/{owner}/{repo}/...scheme (issue/pr/commit):forgejo://repo/{owner}/{repo}/labels→ embedded, bounded list of repo labelsforgejo://repo/{owner}/{repo}/label/{id}→ single repo labelRequirements carried from the resources convention:
operation/<domain>/resources*.go(labels currently live in theissuedomain — keep them together or split alabeldomain, implementer's call); useoperation/resourcehelpers (ParseXxx,Bounded,MapForgejoError).operation/resource.BoundedwithEmbeddedListCapso the truncation sentinel stays consistent with every other resource; requestEmbeddedListCap+1items soBounded's>capcheck fires (mirror theresources.gocomment in the issue domain).docs/design/output-bounding.md: client-controlled bound + resumability + documented parameters.pkg/forgejoclient; private-repo reads return the same403/404MCP mapping viaMapForgejoError.forgejo://owner/{owner}/labels) is a follow-up, gated on org-label CRUD landing.Clients implementing
resources/templates/list+resources/readresolve these URIs directly; others fall back to the tools transparently.Why prioritise
Label management is currently impossible via MCP — any project bootstrapping its own labels (RFC, triage states, priority tiers) is blocked or forced to break the MCP-only policy. The repo-label CRUD is a thin, well-scoped wrapper over SDK methods that already exist in the pinned dependency, so the cost is low and the unblock is immediate.
Acceptance criteria
Tools
create_repo_labelavailable with(owner, repo, name, color, description?)and creates a repo label on Codeberg.edit_repo_labelround-trips a PATCH (only supplied fields change).delete_repo_labelremoves the label;get_repo_labelreads one back by id.colornormalised to Forgejo hex form before send (no 422 on a missing#).pkg/forgejoraw-HTTPDoJSON:create_org_label,edit_org_label,delete_org_label,get_org_label.AGENTS.mdupdated so aToolSearchforlabelsurfaces the new tools.Resource-templates
forgejo://repo/{owner}/{repo}/labelsregistered, returning a bounded embedded list of labels.forgejo://repo/{owner}/{repo}/label/{id}registered, returning a single label.forgejo://org/{org}/labelsregistered, returning a bounded org-label list (bonus: org-label resource landed in same change).operation/issue/resources_label.goand useoperation/resourcehelpers (ParseXxx,Bounded,MapForgejoError).operation/resource.Bounded+EmbeddedListCap(shared truncation sentinel) and satisfiesdocs/design/output-bounding.md(client-controlled bound + resumability + documented params).AGENTS.mdresource table updated to list all three label resource-templates.Notes / non-goals
exclusive/is_archivedlabel fields: out of scope for the first cut (SDK v3 doesn't model them); revisit via raw-HTTP if a use case appears.Two cross-cutting requirements for the OpenSpec change
1. Showboat demo is a merge gate
The OpenSpec change for this issue MUST ship a showboat demo, co-located with the spec, before
openspec sync/archive. Built against a running instance during the implementation PR; read at acceptance without re-execution. No demo → no sync/archive.Demo must exercise the label surface end-to-end:
create_label→get_repo_label/list_repo_labels→edit_label(PATCH only-supplied-fields) →delete_labelround-trip.forgejo://repo/{owner}/{repo}/labels(bounded list) andforgejo://repo/{owner}/{repo}/label/{id}(single).colornormalisation (input without#still succeeds) and the bounded-list truncation sentinel when the repo has >EmbeddedListCaplabels.2. CLI parity — tools AND resources
New surface must be reachable via the
--climode (cmd/cli.go), not only over MCP.--clidispatches by tool name viaregisterToolsWithDomains+cliExec. Just confirmforgejo-mcp --cli create_label --args '{...}'works and the tools appear in--cli list. Add to the acceptance demo.--climode; there is noforgejo://reader). The change must either add a CLI resource-read command (e.g.forgejo-mcp --cli read forgejo://repo/{owner}/{repo}/labels) wired to the same handlers, or explicitly file a follow-up. Resource parity over CLI is NOT free like tool parity is.Both points should land as acceptance-criteria checkboxes in the change's tasks.
CLI resource-parity (point 2 above) split out to #191 — one reader for all resource-templates instead of re-deriving the plumbing per feature. The label resource-template half of this issue depends on #191. Tool CLI parity here stays automatic.