feat: expose repository webhook management tools #136
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#136
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-mcpcurrently has no MCP tools for managing repository webhooks. AToolSearchforhook/webhookagainst the running server returns no matches, and inspecting the published tool list confirms there is nocreate_repo_hook,list_repo_hooks,edit_repo_hookordelete_repo_hooksurface.This gap blocks any workflow that needs to wire a Forgejo / Codeberg repository up to an external system — concretely, my current motivation is registering a Pipelines-as-Code controller webhook on a Codeberg-hosted project. The accepted MCP-only policy in that project forbids falling back to
gh,berg,forgejo-cli,tea, or rawcurl, so the missing tool stops the work cold rather than just being slightly annoying.Forgejo / Gitea API surface to wrap
The upstream API has supported webhook CRUD on a repository for years (Gitea-compatible swagger):
GET/repos/{owner}/{repo}/hooksGET/repos/{owner}/{repo}/hooks/{id}POST/repos/{owner}/{repo}/hooksPATCH/repos/{owner}/{repo}/hooks/{id}DELETE/repos/{owner}/{repo}/hooks/{id}POST/repos/{owner}/{repo}/hooks/{id}/testsThe
CreateHookOptionbody schema (relevant fields):typeaccepts at leastforgejo,gitea,gogs,slack,discord,dingtalk,telegram,matrix,wechatwork,packagist,feishu,msteams— start withforgejosince that is the canonical for Codeberg-style instances. Org-level (/orgs/{org}/hooks) and system-level (/admin/hooks) webhooks have the same shape and could land in a follow-up.Proposed MCP tools
Naming consistent with the existing
mcp__codeberg__create_branch/update_file/delete_branchpatterns:create_repo_hook(owner, repo, url, type, events[], content_type, secret?, http_method?, active?, branch_filter?)→ returns the created hook (incl. its numericid).list_repo_hooks(owner, repo, page?, limit?)→ list of hooks.get_repo_hook(owner, repo, id)→ single hook.edit_repo_hook(owner, repo, id, ...patch fields)→ updated hook.delete_repo_hook(owner, repo, id)→ success.test_repo_hook(owner, repo, id)→ trigger a test delivery (optional, nice-to-have for diagnosing).secretshould be marked sensitive in the schema so MCP hosts can mask it in transcripts.Resource-templates (additive surface)
Per the project's resources convention (
AGENTS.md,openspec/specs/mcp-resources-core), entities should ALSO be exposed asforgejo://resource-templates — additive, instance-portable, and coexisting with the tools above (no tool removed). Themcp-resource-templatesOpenSpec change explicitly defers webhook resources to this issue, so webhooks are tracked here rather than in that change.Proposed resource-templates (singular
reposegment, matching themcp-resources-coreURI scheme — note the deferral text used a pluralreposshorthand; the normative form is singular):forgejo://repo/{owner}/{repo}/hooks→ embedded, bounded list of hooksforgejo://repo/{owner}/{repo}/hooks/{id}→ single hookRequirements carried from the resources convention:
operation/<domain>/resources*.go; use theoperation/resourcehelpers (ParseXxx,Bounded,MapForgejoError).operation/resource.Boundedso the truncation sentinel stays consistent with every other resource.docs/design/output-bounding.md: client-controlled bound + resumability + documented parameters.secretfield MUST be masked in resource reads exactly as in the tool results (same masked hook representation Forgejo returns).pkg/forgejoclient; private-repo reads return the same403/404MCP error mapping the tools use, viaMapForgejoError.Clients that implement
resources/templates/list+resources/readresolve these URIs directly; clients without template support fall back to the tools transparently.Why prioritise
Without this, every CI/CD / observability / mirroring integration that runs through a webhook either needs a human in the Codeberg UI or breaks the project's MCP-only policy. Adding the five core CRUD tools is a small, well-scoped wrapper around an API surface that has been stable in Forgejo/Gitea for a decade.
Acceptance criteria
Tools
create_repo_hookavailable with the parameters listed above and successfully creates a Forgejo-style webhook on Codeberg.list_repo_hooks,get_repo_hook,edit_repo_hook,delete_repo_hookround-trip the created hook.secretparameter not echoed in tool-result payloads (return the masked hook representation Forgejo already gives back).ToolSearchforwebhookactually surfaces the new tools.Resource-templates
forgejo://repo/{owner}/{repo}/hooksregistered as a resource-template returning a bounded, embedded list of hooks.forgejo://repo/{owner}/{repo}/hooks/{id}registered as a resource-template returning a single hook.operation/<domain>/resources*.goand useoperation/resourcehelpers (ParseXxx,Bounded,MapForgejoError).operation/resource.Bounded(shared truncation sentinel) and satisfiesdocs/design/output-bounding.md(client-controlled bound + resumability + documented params).secretmasked in resource reads identically to the tool results.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 webhook surface end-to-end:
create_repo_hook→list_repo_hooks/get_repo_hook→edit_repo_hook→test_repo_hook→delete_repo_hookround-trip.forgejo://repo/{owner}/{repo}/hooks(bounded list) andforgejo://repo/{owner}/{repo}/hooks/{id}(single).secretmasked in both tool results and resource reads.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_repo_hook --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}/hooks) 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 webhook resource-template half of this issue depends on #191. Tool CLI parity here stays automatic.
Session minutes — 2026-06-17
OpenSpec planning complete. PR #257 merged (scheduled auto-merge after CI).
Artifacts created
proposal.mddesign.mdoperation/hook/package, URI scheme, config-key allowlist for secret masking, fire-and-forget test, two-path boundingspecs/repo-webhook-tools/spec.mdtasks.mdbattle-test.mdjournal.jsonlKey design decisions
url,content_type,http_method,branch_filter) — rawConfig map[string]stringMUST NOT be embedded in the payload structEmbeddedListCapforgejo://repo/{owner}/{repo}/hooks{?page,limit}(collection) +forgejo://repo/{owner}/{repo}/hook/{id}(single)Future work filed
test_repo_hook(server-initiated outbound delivery, loop-abuse concern)Next step
Run
/opsx:applyonopenspec/changes/repo-webhook-toolsto start implementation./triage
🤖 Automated triage
Summary: Requests six webhook-management MCP tools (list/get/create/edit/delete/test) plus two forgejo:// resource-templates for repository hooks. The Forgejo API surface is fully mapped, tool signatures are specified, secret-masking requirements defined, acceptance criteria are itemised for both tools and resource-templates, and OpenSpec planning is complete with all design artefacts merged. Implementation is ready to start via /opsx:apply.
Labels applied:
Kind/Feature,Kind/OpenSpec,Reviewed/Confirmed,hermes-readyhermes-issue-worker: agent failed to process this issue (attempt 1/3)
Agent output
Implemented and shipped in commits
fa64928→a9d74f6onmain.What landed
6 MCP tools under
operation/hook/:list_repo_hooks— paginated, no server ceiling (unbounded enumeration path)get_repo_hook— single hook by numeric IDcreate_repo_hook— acceptstype,url,events,secret,content_type,http_method,branch_filter,activeedit_repo_hook— PATCH semantics; only supplied fields changedelete_repo_hook— returns success or MCP not-found errortest_repo_hook— triggers live delivery; description warns callers2 resource templates:
forgejo://repo/{owner}/{repo}/hooks— bounded list (cap 30, sentinellist_repo_hooks)forgejo://repo/{owner}/{repo}/hook/{id}— single hook (singularhook, per the normative URI scheme)Secret never echoed:
safeHook()uses an explicit config-key allowlist (url,content_type,http_method,branch_filter,created_at) —Config["secret"]is never read. This holds for all six tool handlers and both resource handlers.Docs: AGENTS.md resource table updated;
demos/README.mdsection 10 added; showboat demo atopenspec/changes/repo-webhook-tools/specs/repo-webhook-tools/repo-webhook-tools.demo.md.All acceptance criteria checked.