Feature: Complete OpenAPI Documentation for REST API Endpoints Audience: MCPProxy developers adding new REST endpoints or fixing documentation Time to Complete: 10-15 minutes per endpoint
- Go 1.24.0 installed
- swaggo/swag v2.0.0-rc4 installed (
go install github.com/swaggo/swag/v2/cmd/swag@v2.0.0-rc4) - MCPProxy repository cloned
- Familiarity with OpenAPI 3.1 specification basics
Create Go struct types in internal/contracts/ for your endpoint's request/response bodies:
// internal/contracts/config.go (example)
package contracts
import "github.com/smart-mcp-proxy/mcpproxy-go/internal/config"
// GetConfigResponse represents the response for GET /api/v1/config
type GetConfigResponse struct {
Success bool `json:"success"`
Config *config.Config `json:"config"`
}
// ValidateConfigResponse represents the response for POST /api/v1/config/validate
type ValidateConfigResponse struct {
Success bool `json:"success"`
Valid bool `json:"valid"`
Errors []string `json:"errors,omitempty"`
}Tips:
- Use JSON struct tags for field names (
json:"field_name") - Add
omitemptyfor optional fields - Reuse existing types (
config.Config,contracts.ErrorResponse) - Follow naming pattern:
{Action}{Resource}Response(e.g.,GetConfigResponse,ListSessionsResponse)
Add swag comment block above your handler function in internal/httpapi/server.go:
// GetConfig godoc
// @Summary Get current configuration
// @Description Retrieves the current MCPProxy configuration including all server definitions
// @Tags config
// @Produce json
// @Success 200 {object} contracts.GetConfigResponse "Configuration retrieved successfully"
// @Failure 401 {object} contracts.ErrorResponse "Unauthorized - missing or invalid API key"
// @Failure 500 {object} contracts.ErrorResponse "Internal server error"
// @Security ApiKeyAuth
// @Security ApiKeyQuery
// @Router /api/v1/config [get]
func (s *Server) handleGetConfig(w http.ResponseWriter, r *http.Request) {
// Existing implementation (no changes needed)
}Annotation Reference:
| Annotation | Purpose | Example |
|---|---|---|
@Summary |
One-line description | @Summary Get current configuration |
@Description |
Detailed explanation | @Description Retrieves the current MCPProxy configuration... |
@Tags |
Category for grouping | @Tags config |
@Accept |
Request content type | @Accept json (for POST/PUT/PATCH) |
@Produce |
Response content type | @Produce json or text/event-stream |
@Param |
Path/query/body parameters | @Param limit query int false "Maximum results" |
@Success |
Success response | @Success 200 {object} contracts.ResponseType "Description" |
@Failure |
Error response | @Failure 400 {object} contracts.ErrorResponse "Bad request" |
@Security |
Auth requirement | @Security ApiKeyAuth |
@Router |
Route path and method | @Router /api/v1/config [get] |
Parameter Syntax:
@Param name location type required "description"
↓ ↓ ↓ ↓ ↓
id path string true "Server ID or name"
limit query int false "Maximum results (default 50)"
config body config.Config true "Configuration to validate"
Security Annotations:
- All
/api/v1/*endpoints: Add@Security ApiKeyAuthAND@Security ApiKeyQuery - Health endpoints (
/healthz, etc.): Omit@Securityannotations entirely - SSE endpoint (
/events): Add both security annotations (supports query param auth for browsers)
Run the Makefile target to regenerate the OAS from your annotations:
make swaggerWhat This Does:
- Scans all Go files in
internal/httpapi/for swag comments - Discovers contract types in
internal/contracts/ - Generates
oas/swagger.yaml(OpenAPI 3.1 spec) - Generates
oas/docs.go(embedded Go representation)
Expected Output:
✅ OpenAPI 3.1 spec generated: oas/swagger.yaml and oas/docs.go
Start MCPProxy and open Swagger UI:
# Build and run
go build -o mcpproxy ./cmd/mcpproxy
./mcpproxy serve
# Open browser
open http://localhost:8080/swagger/Verification Checklist:
- Your endpoint appears in the correct category (Tags)
- Summary and description are clear
- All parameters show correct types and required status
- Success/failure responses include all status codes
- Lock icon appears next to endpoint name (authentication required)
- "Try it out" button works with valid API key
Testing "Try it out":
- Click the endpoint in Swagger UI
- Click "Try it out"
- Enter API key in the "ApiKeyAuth" or "apikey" field (get from
~/.mcpproxy/mcp_config.json) - Fill in required parameters
- Click "Execute"
- Verify response matches expected schema
Ensure all endpoints are documented:
./scripts/verify-oas-coverage.shExpected Output (All Documented):
✅ All REST endpoints documented in OAS
If Missing Endpoints Detected:
❌ Missing OAS documentation for:
POST /api/v1/secrets/migrate
GET /api/v1/sessions
Fix by repeating Steps 1-3 for the missing endpoints.
// GetStatus godoc
// @Summary Get server status
// @Description Returns current server running state and statistics
// @Tags status
// @Produce json
// @Success 200 {object} contracts.StatusResponse
// @Failure 500 {object} contracts.ErrorResponse
// @Security ApiKeyAuth
// @Security ApiKeyQuery
// @Router /api/v1/status [get]
func (s *Server) handleGetStatus(w http.ResponseWriter, r *http.Request) { ... }// ListToolCalls godoc
// @Summary List tool call history
// @Description Retrieves paginated tool call history with optional session filtering
// @Tags tool-calls
// @Produce json
// @Param limit query int false "Maximum results (default 50, max 1000)"
// @Param offset query int false "Pagination offset (default 0)"
// @Param session_id query string false "Filter by session ID"
// @Success 200 {object} contracts.GetToolCallsResponse
// @Failure 400 {object} contracts.ErrorResponse "Invalid query parameters"
// @Failure 401 {object} contracts.ErrorResponse "Unauthorized"
// @Failure 500 {object} contracts.ErrorResponse "Internal server error"
// @Security ApiKeyAuth
// @Security ApiKeyQuery
// @Router /api/v1/tool-calls [get]
func (s *Server) handleListToolCalls(w http.ResponseWriter, r *http.Request) { ... }// ApplyConfig godoc
// @Summary Apply configuration changes
// @Description Validates, applies, and persists configuration changes
// @Tags config
// @Accept json
// @Produce json
// @Param config body config.Config true "Configuration to apply"
// @Success 200 {object} contracts.ConfigApplyResult "Configuration applied successfully"
// @Failure 400 {object} contracts.ConfigApplyResult "Validation failed (check errors field)"
// @Failure 401 {object} contracts.ErrorResponse "Unauthorized"
// @Failure 403 {object} contracts.ErrorResponse "Management operations disabled"
// @Failure 500 {object} contracts.ErrorResponse "Failed to apply configuration"
// @Security ApiKeyAuth
// @Security ApiKeyQuery
// @Router /api/v1/config/apply [post]
func (s *Server) handleApplyConfig(w http.ResponseWriter, r *http.Request) { ... }// GetServerLogs godoc
// @Summary Get server logs
// @Description Retrieves recent log entries for a specific upstream server
// @Tags servers
// @Produce json
// @Param id path string true "Server ID or name"
// @Param tail query int false "Number of log lines (default 100, max 1000)"
// @Success 200 {object} contracts.GetServerLogsResponse "Server logs retrieved successfully"
// @Failure 400 {object} contracts.ErrorResponse "Bad request (missing server ID)"
// @Failure 401 {object} contracts.ErrorResponse "Unauthorized"
// @Failure 404 {object} contracts.ErrorResponse "Server not found"
// @Failure 500 {object} contracts.ErrorResponse "Internal server error"
// @Security ApiKeyAuth
// @Security ApiKeyQuery
// @Router /api/v1/servers/{id}/logs [get]
func (s *Server) handleGetServerLogs(w http.ResponseWriter, r *http.Request) {
serverID := chi.URLParam(r, "id") // Extract path parameter
// ...
}// StreamEvents godoc
// @Summary Server-Sent Events stream
// @Description Real-time event stream for server status changes and config reloads. Supports API key via query parameter for browser compatibility.
// @Tags events
// @Produce text/event-stream
// @Param apikey query string false "API key for authentication (alternative to header)"
// @Success 200 {string} string "SSE stream of events (event types: servers.changed, config.reloaded)"
// @Failure 401 {object} contracts.ErrorResponse "Unauthorized"
// @Security ApiKeyAuth
// @Security ApiKeyQuery
// @Router /events [get]
func (s *Server) handleStreamEvents(w http.ResponseWriter, r *http.Request) { ... }Note: SSE uses @Produce text/event-stream instead of json.
Error:
⚠️ swag binary not found at /Users/user/go/bin/swag
Solution:
go install github.com/swaggo/swag/v2/cmd/swag@v2.0.0-rc4Possible Causes:
- Missing
godoccomment before handler function - Typo in
@Routerpath or method - Handler not registered in router (check
internal/httpapi/server.goroute registrations) - Forgot to run
make swaggerafter adding annotations
Solution:
- Verify swag comment block syntax (no typos in annotations)
- Ensure
@Routerpath exactly matches chi route registration - Re-run
make swagger - Hard refresh browser (Cmd+Shift+R on macOS)
Possible Causes:
- Missing
@Security ApiKeyAuthand@Security ApiKeyQueryannotations - Endpoint mounted outside API key middleware
Solution:
For /api/v1/* endpoints:
// @Security ApiKeyAuth
// @Security ApiKeyQueryFor health endpoints (correct - no auth required):
// Do NOT add @Security annotationsError:
❌ Missing OAS documentation for:
POST /api/v1/secrets/migrate
Solution:
- Find the handler function for the missing endpoint
- Add swag comment block (see patterns above)
- Run
make swagger - Re-run
./scripts/verify-oas-coverage.sh
- Add swag comments directly above handler functions
- Update documentation when modifying endpoints
- Run
make swaggerbefore committing
// Good
// @Summary Get server logs with pagination
// Bad
// @Summary LogsInclude all possible HTTP status codes:
// @Failure 400 {object} contracts.ErrorResponse "Bad request"
// @Failure 401 {object} contracts.ErrorResponse "Unauthorized"
// @Failure 403 {object} contracts.ErrorResponse "Forbidden"
// @Failure 404 {object} contracts.ErrorResponse "Not found"
// @Failure 500 {object} contracts.ErrorResponse "Internal server error"Don't duplicate schema definitions:
// Good - reuse existing type
// @Success 200 {object} contracts.ErrorResponse
// Bad - inline definition
// @Success 200 {object} object{success=bool,error=string}Always verify endpoints work in Swagger UI before committing.
The MCPProxy CI pipeline includes OAS verification:
# .github/workflows/verify-oas.yml
- name: Verify OAS Coverage
run: |
make swagger-verify
./scripts/verify-oas-coverage.shWhat This Checks:
make swagger-verify: Regenerates OAS and fails if artifacts are dirty (docs not up to date)verify-oas-coverage.sh: Detects undocumented endpoints and fails if any found
Pre-Commit Checklist:
- Added swag annotations to new endpoints
- Defined contract types in
internal/contracts/ - Ran
make swaggerand committed generated files - Verified endpoint in Swagger UI
- Ran
./scripts/verify-oas-coverage.shlocally (all pass) - All tests pass (
./scripts/test-api-e2e.sh)
- swaggo/swag Documentation: https://github.com/swaggo/swag
- OpenAPI 3.1 Specification: https://spec.openapis.org/oas/v3.1.0
- MCPProxy OAS Examples:
internal/httpapi/server.go(existing annotated handlers) - Contract Types Reference:
internal/contracts/package - Swagger UI Local: http://localhost:8080/swagger/ (when MCPProxy is running)
5-Step Process:
- Define contract types in
internal/contracts/ - Add swag annotations above handler functions
- Run
make swaggerto regenerate OAS - Verify in Swagger UI at
http://localhost:8080/swagger/ - Run
./scripts/verify-oas-coverage.shto ensure 100% coverage
Time per Endpoint: ~10-15 minutes Result: Complete, accurate API documentation that prevents drift and enables automated client generation