- demos/org-labels.md: list_org_labels + merged list_repo_labels with scope field, opt-out via include_org_labels=false - demos/bounded-responses.md: file_path on get_pull_request_diff and start_line/end_line on get_file_content, with measured size reductions
4.5 KiB
4.5 KiB
Demo: list_org_labels + merged list_repo_labels
2026-05-12T10:45:00Z by Showboat 0.6.1
Background
Forgejo organizations can carry their own label set, separate from each
repo. add_issue_labels accepts either a repo-scoped or an org-scoped
label ID, but until this release MCP callers could only discover the
repo-scoped half. Issue #125
asked us to close that gap.
Two changes in v2.22.0:
list_org_labels— new tool, lists org-level labels for any organization.list_repo_labels— now merges org labels into its response when the owner is an organization. Each returned label carries ascopefield of"repo"or"org"so callers can tell them apart.
Opt out of the merge with include_org_labels=false.
Setup
export FORGEJO_URL=https://codeberg.org
export FORGEJO_ACCESS_TOKEN=<your-token>
make build
Tool surface
./forgejo-mcp --cli list 2>/dev/null | grep -E 'list_(org|repo)_labels'
list_org_labels List organization-level labels. Each label carries a scope field of "org".
list_repo_labels List repository labels. When the owner is an organization and include_org_labels is true (default), org-level labels are merged into the response. Each label carries a scope field of "repo" or "org".
1. list_org_labels — org-only discovery
./forgejo-mcp --cli list_org_labels \
--args '{"org":"forgejo"}' 2>/dev/null | python3 -c "
import sys, json
data = json.loads(json.load(sys.stdin)[0]['text'])
labels = data.get('Result', [])
print(f'Total org labels: {len(labels)}')
for l in labels[:5]:
print(f\" id={l['id']:7d} scope={l['scope']:<4s} {l['name']}\")
"
Total org labels: 17
id= 223765 scope=org User research - Accessibility
id= 440466 scope=org User research - Blocked
id= 440496 scope=org User research - Community
id= 209569 scope=org User research - Config (instance)
id= 209666 scope=org User research - Errors
2. list_repo_labels — merged response (default)
./forgejo-mcp --cli list_repo_labels \
--args '{"owner":"forgejo","repo":"forgejo","limit":50}' 2>/dev/null | python3 -c "
import sys, json
from collections import Counter
data = json.loads(json.load(sys.stdin)[0]['text'])
labels = data.get('Result', [])
scopes = Counter(l['scope'] for l in labels)
print(f'Merged labels: {len(labels)} | by scope: {dict(scopes)}')
print('Sample:')
for l in [labels[0], labels[20], labels[-1]]:
print(f\" id={l['id']:7d} scope={l['scope']:<4s} {l['name']}\")
"
Merged labels: 67 | by scope: {'repo': 50, 'org': 17}
Sample:
id= 204851 scope=repo arch/riscv64
id= 223008 scope=repo dependency-upgrade
id= 208225 scope=org User research - Settings (in-app)
3. list_repo_labels — opt out with include_org_labels=false
./forgejo-mcp --cli list_repo_labels \
--args '{"owner":"forgejo","repo":"forgejo","limit":50,"include_org_labels":false}' 2>/dev/null | python3 -c "
import sys, json
from collections import Counter
data = json.loads(json.load(sys.stdin)[0]['text'])
labels = data.get('Result', [])
print(f'Total: {len(labels)} | scopes: {dict(Counter(l[\"scope\"] for l in labels))}')
"
Total: 50 | scopes: {'repo': 50}
4. End-to-end: autonomous label triage across scopes
An agent can now build a single label lookup table that spans both scopes,
then call add_issue_labels with whichever ID matches — Forgejo applies
org-scoped and repo-scoped labels through the same endpoint:
list_repo_labelswith defaultinclude_org_labels=true→ unified{id, name, scope}table.- Match issue title/body against label names (e.g.
"User research - Labels"→ id206178, scopeorg). add_issue_labelswith the discovered ID — no separate code path for org labels.
For non-org owners (regular users) list_repo_labels returns only
repo-scoped labels; the org fetch is skipped automatically. No caller
change needed.
Edge cases
- Owner is a user, not an org. The
/orgs/{user}/labelsendpoint returns 404; the handler maps that to an empty org-label slice and the response still succeeds withscope:"repo"entries only. - Org has no labels. Empty
orgslice merged in — response is effectively just the repo labels. - Pagination.
pageandlimitapply to both the repo and the org fetch. To paginate across all 67 labels above, walk pages of the merged response.