Feature: 021-request-id-logging Date: 2026-01-07
This feature introduces request-scoped context and standardized error responses with request IDs. No database schema changes are required - these are runtime/API structures.
Purpose: Request-scoped context carrying the request ID through the handler chain.
Attributes:
| Field | Type | Description |
|---|---|---|
| request_id | string | Unique identifier for this request (UUID or client-provided) |
| logger | *zap.Logger | Logger instance with request_id field pre-set |
| start_time | time.Time | When the request was received |
Constraints:
request_idfollows pattern^[a-zA-Z0-9_-]{1,256}$- Created in middleware at start of request
- Propagated via Go context.Context
Go Implementation:
type RequestContext struct {
RequestID string
Logger *zap.Logger
StartTime time.Time
}
// Context keys
type contextKey string
const RequestContextKey contextKey = "request_context"
// Helper to get from context
func GetRequestContext(ctx context.Context) *RequestContext {
if rc, ok := ctx.Value(RequestContextKey).(*RequestContext); ok {
return rc
}
return nil
}
func GetRequestID(ctx context.Context) string {
if rc := GetRequestContext(ctx); rc != nil {
return rc.RequestID
}
return ""
}Purpose: Standard JSON structure for all API error responses.
Attributes:
| Field | Type | Description |
|---|---|---|
| error | string | Machine-readable error code |
| message | string | Human-readable error description |
| request_id | string | Request ID for log correlation |
| suggestion | string (optional) | Actionable remediation hint |
| details | object (optional) | Additional error-specific data |
Constraints:
request_idis always presenterroruses snake_case codessuggestionis populated for actionable errors
Example (validation error):
{
"error": "server_not_found",
"message": "Server 'google-drvie' not found",
"request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"suggestion": "Check server name spelling. Did you mean 'google-drive'?",
"details": {
"available_servers": ["google-drive", "github-server"]
}
}Example (internal error):
{
"error": "internal_error",
"message": "An unexpected error occurred",
"request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890"
}Go Implementation:
type ErrorResponse struct {
Error string `json:"error"`
Message string `json:"message"`
RequestID string `json:"request_id"`
Suggestion string `json:"suggestion,omitempty"`
Details map[string]interface{} `json:"details,omitempty"`
}
func NewErrorResponse(ctx context.Context, code, message string) *ErrorResponse {
return &ErrorResponse{
Error: code,
Message: message,
RequestID: GetRequestID(ctx),
}
}Purpose: Extended structured log entry with request ID field.
Attributes:
| Field | Type | Description |
|---|---|---|
| level | string | Log level (debug, info, warn, error) |
| msg | string | Log message |
| timestamp | string | ISO 8601 timestamp |
| request_id | string (optional) | Request ID if in request context |
| correlation_id | string (optional) | OAuth flow correlation ID |
| server | string (optional) | Server name if applicable |
| ... | various | Other contextual fields |
Example (request-scoped log):
{
"level": "info",
"msg": "handling server list request",
"timestamp": "2026-01-07T10:30:00Z",
"request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
"path": "/api/v1/servers",
"method": "GET"
}Example (OAuth flow log with both IDs):
{
"level": "info",
"msg": "OAuth callback received",
"timestamp": "2026-01-07T10:30:05Z",
"request_id": "req-abc123",
"correlation_id": "oauth-def456",
"server": "google-drive",
"state": "authenticating"
}Note: LogEntry is not a Go struct but represents the JSON structure produced by Zap logger.
Purpose: Parameters for log retrieval filtering.
Attributes:
| Field | Type | Description |
|---|---|---|
| request_id | string (optional) | Filter by request ID |
| correlation_id | string (optional) | Filter by OAuth correlation ID |
| server | string (optional) | Filter by server name |
| level | string (optional) | Filter by log level |
| since | time.Time (optional) | Start time filter |
| until | time.Time (optional) | End time filter |
| limit | int | Maximum entries to return (default 100) |
Example API Request:
GET /api/v1/logs?request_id=abc123&limit=50
Go Implementation:
type LogQueryParams struct {
RequestID string `json:"request_id,omitempty"`
CorrelationID string `json:"correlation_id,omitempty"`
Server string `json:"server,omitempty"`
Level string `json:"level,omitempty"`
Since time.Time `json:"since,omitempty"`
Until time.Time `json:"until,omitempty"`
Limit int `json:"limit,omitempty"`
}| Header | Direction | Description |
|---|---|---|
X-Request-Id |
Client → Server | Optional client-provided request ID |
Validation:
- Pattern:
^[a-zA-Z0-9_-]{1,256}$ - If invalid or missing: server generates UUID v4
| Header | Direction | Description |
|---|---|---|
X-Request-Id |
Server → Client | Request ID (client-provided or generated) |
Behavior:
- Always present in ALL responses (success and error)
- Set in middleware before handler execution
- Matches
request_idin error response body
All clients receive the same X-Request-Id header and error response structure:
| Client | On Error | Action |
|---|---|---|
| CLI | Print request_id + suggestion | mcpproxy logs --request-id <id> |
| Tray | Notification with ID | Copy button, link to logs |
| Web UI | Modal with ID | Copy button, inline logs link |
┌──────────────────────┐
│ HTTP Request │
│ X-Request-Id: ? │
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ RequestID │
│ Middleware │
│ (generate/validate)│
└──────────┬───────────┘
│
▼
┌──────────────────────┐
│ RequestContext │
│ + Logger w/ ID │
└──────────┬───────────┘
│
┌─────┴─────┐
│ │
▼ ▼
┌─────────┐ ┌──────────────────┐
│ Handler │ │ Logs with │
│ Logic │ │ request_id field │
└────┬────┘ └──────────────────┘
│
▼
┌──────────────────────┐
│ Response │
│ X-Request-Id: xxx │
│ + request_id body │
└──────────────────────┘
- No database changes: Request IDs are transient per-request
- Log storage: Activity log (BBolt) extended with request_id field
- Log retention: Follows existing activity log retention policy
- Index: Activity log already indexed; request_id filtering via existing mechanisms
When OAuth login endpoint is called:
{
"success": true,
"server_name": "google-drive",
"correlation_id": "oauth-abc123",
"request_id": "req-xyz789",
"auth_url": "https://...",
"browser_opened": true
}Both IDs appear in logs:
request_id: Find logs for this specific HTTP requestcorrelation_id: Find logs for entire OAuth flow (including callbacks)