Implements openspec change add-releases-support (forgejo-mcp-0ep). - operation/release/: 8 release tools (list/get/create/edit/delete, state filter, by-tag variants) + 6 attachment tools (list/get/ download/create/edit/delete), wired in operation.go + cmd/cli.go - State filter (all|draft|prerelease|published) applied client-side; list_release_attachments sliced client-side (no SDK pagination) - download_release_attachment reuses MaxInlineDownloadBytes cap and BlobResourceContents inline shape from the issue/comment domains - 19 unit tests covering pagination, state guard, partial-edit PATCH, base64 decode guard, client-side slice boundaries, over/at-cap download - params: ReleaseID/Tag/TagName/TargetCommitish/Draft/Prerelease/State - README tools table + DEVELOPER.md domain list updated - demos/release-management.md: read-only walkthrough with real Codeberg output; write-tool surface; autonomous release-notes workflow
5.9 KiB
Developer Guide
This guide covers building, developing, and contributing to the Forgejo MCP Server.
Prerequisites
- Go 1.24 or later
- make (optional, for convenience commands)
Building
Using Make
make build # Build the binary (outputs ./forgejo-mcp)
make vendor # Tidy and verify Go module dependencies
Using Go Directly
go build -v # Build the binary
go mod tidy # Tidy dependencies
Running Locally
# stdio mode (for MCP client integration)
./forgejo-mcp --transport stdio --url https://forgejo.example.org --token <token>
# SSE mode (for HTTP-based clients)
./forgejo-mcp --transport sse --url https://forgejo.example.org --token <token> --sse-port 8080
# With debug logging
./forgejo-mcp --transport sse --url <url> --token <token> --debug
Environment variables: FORGEJO_URL, FORGEJO_ACCESS_TOKEN, FORGEJO_DEBUG, FORGEJO_USER_AGENT
CLI options: --url, --token, --transport, --sse-port, --user-agent
Architecture
This is an MCP (Model Context Protocol) server that exposes Forgejo API operations as tools for AI assistants.
Core Flow
main.go → cmd/cmd.go (CLI parsing) → operation/operation.go (tool registration) → operation/{domain}/*.go (tool handlers)
Directory Structure
| Directory | Purpose |
|---|---|
cmd/ |
CLI entry point and command parsing |
operation/ |
MCP tool definitions and handlers, organized by domain |
operation/issue/ |
Issue-related tools |
operation/pull/ |
Pull request tools |
operation/release/ |
Release and release-attachment tools |
operation/repo/ |
Repository and branch tools |
operation/search/ |
Search tools (users, repos, teams) |
operation/user/ |
User info tools |
operation/version/ |
Server version tool |
pkg/forgejo/ |
Singleton Forgejo SDK client wrapper |
pkg/to/ |
Response formatting helpers (TextResult, ErrorResult) |
pkg/params/ |
Shared parameter descriptions for tool definitions |
pkg/flag/ |
Global configuration state |
pkg/log/ |
Structured logging utilities |
Adding a New Tool
Step 1: Create or Modify a Domain File
Tools are organized by domain in operation/{domain}/. Create a new file or add to an existing one.
Step 2: Define the Tool
package mydomain
import (
"context"
"fmt"
"codeberg.org/goern/forgejo-mcp/v2/pkg/forgejo"
"codeberg.org/goern/forgejo-mcp/v2/pkg/params"
"codeberg.org/goern/forgejo-mcp/v2/pkg/to"
"github.com/mark3labs/mcp-go/mcp"
"github.com/mark3labs/mcp-go/server"
)
// Tool definition
var MyTool = mcp.NewTool(
"my_tool_name",
mcp.WithDescription("What this tool does"),
mcp.WithString("owner", mcp.Required(), mcp.Description(params.Owner)),
mcp.WithString("repo", mcp.Required(), mcp.Description(params.Repo)),
mcp.WithNumber("limit", mcp.Description("Page size"), mcp.DefaultNumber(20)),
)
// Handler function
func MyToolFn(ctx context.Context, req mcp.CallToolRequest) (*mcp.CallToolResult, error) {
// Extract parameters (numbers come as float64)
owner, _ := req.Params.Arguments["owner"].(string)
repo, _ := req.Params.Arguments["repo"].(string)
limit, _ := req.Params.Arguments["limit"].(float64)
// Call Forgejo API
result, _, err := forgejo.Client().SomeMethod(owner, repo, int(limit))
if err != nil {
return to.ErrorResult(fmt.Errorf("operation failed: %v", err))
}
// Return formatted result
return to.TextResult(result)
}
Step 3: Register the Tool
Add registration in the domain's file:
func RegisterTool(s *server.MCPServer) {
s.AddTool(MyTool, MyToolFn)
}
Step 4: Wire Up New Domains
If you created a new domain, import and register it in operation/operation.go:
import "codeberg.org/goern/forgejo-mcp/v2/operation/mydomain"
func RegisterTools(s *server.MCPServer) {
// ... existing registrations
mydomain.RegisterTool(s)
}
Key Patterns
Parameter Handling
- String parameters:
value, _ := req.Params.Arguments["param"].(string) - Number parameters:
value, _ := req.Params.Arguments["num"].(float64)(always float64) - Optional with defaults: Check if value exists before using
Response Formatting
Use helpers from pkg/to/:
// Success response
return to.TextResult(data)
// Error response
return to.ErrorResult(fmt.Errorf("something went wrong: %v", err))
Shared Parameter Descriptions
Reuse descriptions from pkg/params/ for consistency:
mcp.WithString("owner", mcp.Required(), mcp.Description(params.Owner))
mcp.WithString("repo", mcp.Required(), mcp.Description(params.Repo))
mcp.WithNumber("page", mcp.Description(params.Page), mcp.DefaultNumber(1))
Dependencies
| Package | Purpose |
|---|---|
codeberg.org/mvdkleijn/forgejo-sdk/forgejo/v2 |
Forgejo API client |
github.com/mark3labs/mcp-go |
MCP protocol implementation |
github.com/spf13/cobra |
CLI framework |
Testing
Run with debug mode to troubleshoot issues:
FORGEJO_DEBUG=true ./forgejo-mcp --transport stdio --url <url> --token <token>
Blocked Features
Some planned features are blocked on upstream API or SDK support:
| Feature | Status | Details |
|---|---|---|
| Wiki support | Blocked | Waiting for forgejo-sdk wiki API |
| Projects/Kanban | Blocked | Requires Gitea 1.26.0 API |
See docs/plans/ for detailed status:
wiki-support.md- Wiki API implementation planprojects-support.md- Projects/Kanban implementation plan
Contributing
- Fork the repository on Codeberg
- Create a feature branch
- Make your changes following the patterns above
- Test locally with both stdio and SSE modes
- Submit a pull request
Code Style
- Follow standard Go conventions
- Use meaningful variable names
- Add tool descriptions that clearly explain what each tool does
- Reuse parameter descriptions from
pkg/params/where applicable