SKILL.md called `exclusive` "an org-labels-only flag" and concluded that applying a Kind/*, Priority/* or Status/* label "must replace the one already there, not stack on it". Live `list_repo_labels` on this repo returns `exclusive` on every REPO label: all seven Kind/* plus Compat/Breaking are false, while Priority/*, Reviewed/* and Status/* are true. So the field is not org-only, and the replace rule would strip a valid Kind/* label — a destructive write from a false premise. The nuance kept: label_write's `exclusive` parameter genuinely is annotated org-only, so that row was schema-accurate. The error was generalising a write-parameter restriction into a claim about where the field exists. The row is qualified rather than deleted. The rule is now per-label: where exclusive is true the server drops the sibling itself, so do not pre-remove; where it is false the label is legitimately stackable. Also fixes the org-label fallback, which treated any list_org_labels failure as proof the owner is a user account with no org pool and said so was "an answer, not an error to report". Under the token scopes this skill declares the call fails with required=[read:organization] before any org-vs-user determination is made, so a capability gap was being reported as an absent label. Scope errors are now distinguished and reported. Verified live against gitea-mcp v1.7.0, read-only calls. Refs #99
4.7 KiB
topic, source_keys
| topic | source_keys | ||
|---|---|---|---|
| labels |
|
Label operations
Execution detail for label_read and label_write. Both tools operate on either repo-scoped
or org-scoped labels — never both in one call. Pick the method family (*_repo_label* vs.
*_org_label*) that matches the target, and pass owner+repo or org accordingly.
Verified live schemas
label_read — required: method.
| Param | Type | Notes |
|---|---|---|
method |
string (enum) | "list_repo_labels" | "get_repo_label" | "list_org_labels" |
owner |
string | for repo methods |
repo |
string | for repo methods |
org |
string | for org methods |
id |
number | label ID, required for "get_repo_label" |
page |
number | default 1 |
per_page |
number | default 30 |
label_write — required: method.
| Param | Type | Notes |
|---|---|---|
method |
string (enum) | "create_repo_label" | "edit_repo_label" | "delete_repo_label" | "create_org_label" | "edit_org_label" | "delete_org_label" |
owner |
string | for repo methods |
repo |
string | for repo methods |
org |
string | for org methods |
id |
number | for edit/delete |
name |
string | required for create |
color |
string | hex #RRGGBB, required for create |
description |
string | optional |
exclusive |
boolean | accepted as a write param on org creates only — repo labels still carry and enforce exclusive, set outside this tool surface |
is_archived |
boolean | repo labels only |
Note: unlike milestone_read/milestone_write, owner/repo/org are not schema-required on
either label tool — only method is. Passing none for a repo/org method still fails, just as a
runtime error from Gitea rather than a client-side validation error.
List repo labels
label_read method: "list_repo_labels" owner: <owner> repo: <repo> per_page: 50
Paginate (page: 1, 2, ...) until the returned count is less than per_page. This is the only way
to build a complete name → ID map — there is no lookup-by-name endpoint.
Every returned repo label carries its own exclusive boolean; the field is not org-only. Verified on
Defame1297/holocron: all Priority/*, Reviewed/* and Status/* labels are exclusive: true,
while all Kind/* labels and Compat/Breaking are exclusive: false. Where it is true Gitea
enforces one-label-per-scope server-side; where it is false labels in that scope stack legitimately.
Read the field — never infer exclusivity from the / in a name.
Get one label
label_read method: "get_repo_label" owner: <owner> repo: <repo> id: <id>
Resolve a name to an ID
There is no direct name lookup. List all repo labels (paginating if needed), scan for a
case-insensitive name match, and extract id. Both pools can apply to one issue: if the name is
not in list_repo_labels, also check list_org_labels before reporting it unresolved. That method
takes org, not owner/repo — pass the repo's owner as org, which is what it means when the
owner is an organisation. Its failure modes are not interchangeable. token does not have at least one of required scope(s), required=[read:organization] means the org pool was never queried — report
that missing scope rather than reporting the label unresolved. Only a not-found response means the
owner is a user account with no org pool, making the miss a real miss.
Resolution is the required first step before any label application on an issue or PR — the actual
add_labels/replace_labels/remove_label call lives in gitea-issues/gitea-prs via
issue_write/pull_request_write, which take numeric IDs only.
Create a label
label_write method: "create_repo_label"
owner: <owner> repo: <repo>
name: "Kind/Bug"
color: "#d73a4a"
description: "Confirmed bug"
For an org label, use method: "create_org_label" with org: instead of owner/repo, and
exclusive: true if the label belongs to a mutually-exclusive scope group. label_write accepts
exclusive on org methods only, so a repo label's exclusivity cannot be set or cleared through this
tool — it is set in the Gitea UI or against the REST API directly, and read back via
list_repo_labels.
Edit a label
label_write method: "edit_repo_label" owner: <owner> repo: <repo> id: <id> color: "#ff0000"
Only pass the fields being changed — id plus any of name/color/description/is_archived.
Delete a label
label_write method: "delete_repo_label" owner: <owner> repo: <repo> id: <id>
Deleting a label does not remove it from historical issue/PR timeline events — it disappears only from current label lists.