230 lines
7.0 KiB
Markdown
230 lines
7.0 KiB
Markdown
# AGENTS.md
|
|
|
|
This file provides guidance to AI coding agents when working with code in this repository.
|
|
|
|
## Development Commands
|
|
|
|
**Build**: `make build` - Build the gitea-mcp binary
|
|
**Install**: `make install` - Build and install to GOPATH/bin
|
|
**Clean**: `make clean` - Remove build artifacts
|
|
**Test**: `go test ./...` - Run all tests
|
|
**Hot reload**: `make dev` - Start development server with hot reload (requires air)
|
|
**Dependencies**: `make vendor` - Tidy and verify module dependencies
|
|
|
|
## Architecture Overview
|
|
|
|
This is a **Gitea MCP (Model Context Protocol) Server** written in Go that provides MCP tools for interacting with Gitea repositories, issues, pull requests, users, and more.
|
|
|
|
**Core Components**:
|
|
|
|
- `main.go` + `cmd/cmd.go`: CLI entry point and flag parsing
|
|
- `operation/operation.go`: Main server setup and tool registration
|
|
- `pkg/tool/tool.go`: Tool registry with read/write categorization
|
|
- `operation/*/`: Individual tool modules (user, repo, issue, pull, search, wiki, etc.)
|
|
|
|
**Transport Modes**:
|
|
|
|
- **stdio** (default): Standard input/output for MCP clients
|
|
- **HTTP**: HTTP server mode on configurable port (default 8080)
|
|
|
|
**Authentication**:
|
|
|
|
- Global token via `--token` flag or `GITEA_ACCESS_TOKEN` env var
|
|
- HTTP mode supports per-request Bearer token override in Authorization header
|
|
- Token precedence: HTTP Authorization header > CLI flag > environment variable
|
|
|
|
**Tool Organization**:
|
|
|
|
- Tools are categorized as read-only or write operations
|
|
- `--read-only` flag exposes only read tools
|
|
- Tool modules register via `Tool.RegisterRead()` and `Tool.RegisterWrite()`
|
|
|
|
**Key Configuration**:
|
|
|
|
- Default Gitea host: `https://gitea.com` (override with `--host` or `GITEA_HOST`)
|
|
- Environment variables can override CLI flags: `MCP_MODE`, `GITEA_READONLY`, `GITEA_DEBUG`, `GITEA_INSECURE`
|
|
- Logs are written to `~/.gitea-mcp/gitea-mcp.log` with rotation
|
|
|
|
## Available Tools
|
|
|
|
The server provides 40+ MCP tools covering:
|
|
|
|
- **User**: get_my_user_info, get_user_orgs, search_users
|
|
- **Repository**: create_repo, fork_repo, list_my_repos, search_repos
|
|
- **Branches/Tags**: create_branch, delete_branch, list_branches, create_tag, list_tags
|
|
- **Files**: get_file_content, create_file, update_file, delete_file, get_dir_content
|
|
- **Issues**: create_issue, list_repo_issues, create_issue_comment, edit_issue
|
|
- **Pull Requests**: create_pull_request, list_repo_pull_requests, get_pull_request_by_index
|
|
- **Releases**: create_release, list_releases, get_latest_release
|
|
- **Wiki**: create_wiki_page, update_wiki_page, list_wiki_pages
|
|
- **Search**: search_repos, search_users, search_org_teams
|
|
- **Version**: get_gitea_mcp_server_version
|
|
|
|
## Error Handling and Logging
|
|
|
|
The codebase provides comprehensive error handling and structured logging with context support.
|
|
|
|
### Enhanced Error Handling
|
|
|
|
The `pkg/errors` package provides enhanced error handling with context and fluent API:
|
|
|
|
```go
|
|
import "gitea.com/gitea/gitea-mcp/pkg/errors"
|
|
|
|
// Basic error translation
|
|
err := someGiteaOperation()
|
|
if err != nil {
|
|
return errors.TranslateError(err, map[string]string{
|
|
"operation": "GetFile",
|
|
"owner": owner,
|
|
"repo": repo,
|
|
"path": path,
|
|
})
|
|
}
|
|
|
|
// Using fluent API for building error context
|
|
err := someGiteaOperation()
|
|
if err != nil {
|
|
return errors.TranslateError(err, nil).
|
|
WithOperation("GetFile").
|
|
WithParam("owner", owner).
|
|
WithParam("repo", repo).
|
|
WithParam("path", path)
|
|
}
|
|
|
|
// Error includes automatic timestamp
|
|
enhanced := err.(*errors.EnhancedError)
|
|
fmt.Printf("Error occurred at: %v\n", enhanced.Timestamp)
|
|
|
|
// Format error for logging (human-readable)
|
|
fmt.Println(enhanced.Format())
|
|
// Output: Operation: GetFile | Error: File or directory not found | Category: file | Context: owner=gitea, path=README.md | Original: GetContents failed
|
|
|
|
// Format error for structured logging (JSON-like)
|
|
fmt.Println(enhanced.FormatDetailed())
|
|
// Output:
|
|
// {
|
|
// "error": "File or directory not found",
|
|
// "category": "file",
|
|
// "operation": "GetFile",
|
|
// "timestamp": "2024-01-15T10:30:00Z",
|
|
// "context": {
|
|
// "owner": "gitea",
|
|
// "path": "README.md"
|
|
// },
|
|
// "original": "GetContents failed with status 404"
|
|
// }
|
|
```
|
|
|
|
### Error Category Checking
|
|
|
|
```go
|
|
// Check error categories
|
|
if errors.IsNotFound(err) {
|
|
// Handle not found (404, "not found" messages)
|
|
}
|
|
|
|
if errors.IsAuthError(err) {
|
|
// Handle auth errors (401, 403)
|
|
}
|
|
|
|
if errors.IsTimeout(err) {
|
|
// Handle timeout errors
|
|
}
|
|
|
|
if errors.IsNetworkError(err) {
|
|
// Handle network connectivity issues
|
|
}
|
|
|
|
// Check specific HTTP status codes
|
|
if errors.IsUnauthorized(err) {
|
|
// Handle 401
|
|
}
|
|
|
|
if errors.IsForbidden(err) {
|
|
// Handle 403
|
|
}
|
|
|
|
if errors.IsServerError(err) {
|
|
// Handle 5xx errors
|
|
}
|
|
```
|
|
|
|
### Structured Logging with Context
|
|
|
|
The `pkg/log` package provides request-scoped structured logging with correlation IDs:
|
|
|
|
```go
|
|
import (
|
|
"context"
|
|
"gitea.com/gitea/gitea-mcp/pkg/log"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
// Create context with correlation ID for request tracing
|
|
ctx := log.WithCorrelationID(context.Background(), "req-12345")
|
|
|
|
// Add operation name to context
|
|
ctx = log.WithOperation(ctx, "GetFile")
|
|
|
|
// Create logger with context
|
|
logger := log.WithContext(ctx)
|
|
|
|
// Log messages - correlation_id and operation are automatically included
|
|
logger.Info("processing request")
|
|
logger.Error("operation failed", zap.Error(err))
|
|
|
|
// Log with additional fields
|
|
logger.Info("file retrieved",
|
|
zap.String("owner", owner),
|
|
zap.String("repo", repo),
|
|
zap.String("path", path),
|
|
)
|
|
|
|
// Operation logging with timing
|
|
op := log.StartOperation(ctx, "CreatePullRequest")
|
|
op.Start("beginning pull request creation")
|
|
// ... do work ...
|
|
op.Success("pull request created successfully")
|
|
// Or on failure:
|
|
op.Failure("failed to create pull request", err)
|
|
```
|
|
|
|
### REST API Logging
|
|
|
|
The `pkg/gitea/rest.go` automatically logs all API requests with context:
|
|
|
|
```go
|
|
import (
|
|
"context"
|
|
"gitea.com/gitea/gitea-mcp/pkg/log"
|
|
"gitea.com/gitea/gitea-mcp/pkg/gitea"
|
|
)
|
|
|
|
// Create context with operation name for tracing
|
|
ctx := log.WithOperation(context.Background(), "GetRepository")
|
|
|
|
// All API calls are automatically logged with:
|
|
// - operation name
|
|
// - HTTP method
|
|
// - request path (no sensitive data)
|
|
// - response status code
|
|
// - duration
|
|
// - correlation ID
|
|
status, err := gitea.DoJSON(ctx, "GET", fmt.Sprintf("repos/%s/%s", owner, repo), nil, nil, &repo)
|
|
|
|
// Logs will include:
|
|
// - Debug: "sending API request" with operation, method, path, correlation_id
|
|
// - Debug: "API request completed" with status_code and duration on success
|
|
// - Error: "API request returned error status" with details on failure
|
|
```
|
|
|
|
### Common Development Patterns
|
|
|
|
**Testing**: Use `go test ./operation -run TestFunctionName` for specific tests
|
|
|
|
**Token Context**: HTTP requests use `pkg/context.TokenContextKey` for request-scoped token access
|
|
|
|
**Flag Access**: All packages access configuration via global variables in `pkg/flag/flag.go`
|
|
|
|
**Graceful Shutdown**: HTTP mode implements graceful shutdown with 10-second timeout on SIGTERM/SIGINT |