Proposal, design, specs, and tasks for adding 15 MCP tools covering org CRUD, membership, and team operations. Tracks issue #92.
4.4 KiB
Context
Forgejo MCP currently supports six operational domains: user, repo, issue, pull, search, actions, and version. Each domain follows the same pattern:
- A package under
operation/<domain>/defines tools viamcp.NewTool()and handler functions - A
RegisterTool(s *server.MCPServer)function wires them to the MCP server operation/operation.gocalls each domain's registrationcmd/cli.gomirrors the same registration with domain tagging for--cli listgrouping
The Forgejo Go SDK (codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2) provides full organization API coverage. The singleton client in pkg/forgejo/ is already used by all domains.
Shared parameter descriptions live in operation/params/params.go — the Org and User constants already exist.
Goals / Non-Goals
Goals:
- Add 15 MCP tools for organization management (CRUD, membership, teams)
- Follow existing domain patterns exactly — no new abstractions
- Available via both MCP and CLI with zero extra CLI wiring (CLI dispatches to MCP handlers)
- Consistent parameter naming and response formatting with existing tools
Non-Goals:
- Admin-only endpoints (
AdminCreateOrg,AdminListOrgs) — these require admin tokens and are a different use case - Organization webhooks — complex configuration, separate feature
- Organization Actions secrets/variables — already partially covered by the actions domain
- Organization avatar management — low priority, not in SDK as structured API
- Organization labels — blocked on SDK support
Decisions
1. Single operation/org/ package with file-per-concern
Three files: org.go (CRUD), membership.go (membership), teams.go (teams). One RegisterTool() entry point.
Why over a flat single file: 15 tools × ~50 lines each = ~750 lines. Splitting by concern matches operation/repo/ which splits into repo.go, file.go, branch.go, commit.go.
2. Tool naming: {verb}_{noun} with org prefix
Names: create_org, get_org, list_my_orgs, list_user_orgs, edit_org, delete_org, list_org_members, check_org_membership, remove_org_member, list_org_teams, create_org_team, add_team_member, remove_team_member, add_team_repo, remove_team_repo.
Why: Matches existing conventions (create_repo, fork_repo, list_my_repos). The org prefix disambiguates from user-level operations.
3. Team operations use team ID (int64) for mutations, org name for listing
list_org_teams takes org (string). add_team_member, remove_team_member, add_team_repo, remove_team_repo take team_id (number) — this matches the SDK which uses int64 team IDs for these operations.
Why over name-based lookup: The SDK methods (AddTeamMember(id, user), AddTeamRepository(id, org, repo)) use numeric IDs directly. Adding a name→ID lookup would require an extra API call and introduce ambiguity (team names aren't unique across orgs).
4. Reuse pkg/to for response formatting
All handlers return to.TextResult(obj) for success and to.ErrorResult(err) for errors, matching every existing tool.
5. Pagination on list endpoints
list_my_orgs, list_user_orgs, list_org_members, list_org_teams accept page and limit parameters using the existing params.Page / params.Limit descriptions and the same defaults (page=1, limit=100).
6. Visibility as string enum
create_org and edit_org accept visibility as a string: "public", "limited", or "private". The SDK's VisibleType is a string alias, so no conversion is needed.
Risks / Trade-offs
[Risk] Delete org is destructive → Tool description will include a clear warning. No confirmation mechanism exists in MCP protocol, so the tool description must convey severity. This matches the existing delete_branch and delete_file patterns.
[Risk] Team ID discovery requires two steps → Users must call list_org_teams to find a team ID before using team mutation tools. This is inherent to the SDK design and documented in tool descriptions. Alternative (name-based lookup) was rejected per Decision 3.
[Trade-off] No admin endpoints → Users with admin tokens cannot create orgs on behalf of other users. This keeps the scope focused and avoids requiring admin-level permissions for the basic use case. Can be added later as a separate feature.
[Trade-off] No webhook/actions/label management → Keeps the initial scope manageable (15 tools). These can be added incrementally in future changes.