Feature: 016-activity-log-backend Date: 2025-12-26
This guide helps developers get started implementing the Activity Log Backend feature. It covers the key integration points and provides working examples.
- Go 1.24+
- Running mcpproxy instance
- API key for authentication
| File | Purpose |
|---|---|
internal/storage/activity.go |
BBolt storage operations |
internal/storage/activity_models.go |
Data types |
internal/runtime/events.go |
Event type definitions |
internal/runtime/activity_service.go |
Recording service |
internal/httpapi/activity.go |
REST handlers |
internal/contracts/activity.go |
API types |
internal/server/mcp.go |
Emit events on tool calls |
// internal/storage/activity_models.go
type ActivityType string
const (
ActivityTypeToolCall ActivityType = "tool_call"
ActivityTypePolicyDecision ActivityType = "policy_decision"
ActivityTypeQuarantineChange ActivityType = "quarantine_change"
ActivityTypeServerChange ActivityType = "server_change"
)
type ActivityRecord struct {
ID string `json:"id"`
Type ActivityType `json:"type"`
ServerName string `json:"server_name,omitempty"`
ToolName string `json:"tool_name,omitempty"`
Arguments map[string]interface{} `json:"arguments,omitempty"`
Response string `json:"response,omitempty"`
ResponseTruncated bool `json:"response_truncated,omitempty"`
Status string `json:"status"`
ErrorMessage string `json:"error_message,omitempty"`
DurationMs int64 `json:"duration_ms,omitempty"`
Timestamp time.Time `json:"timestamp"`
SessionID string `json:"session_id,omitempty"`
RequestID string `json:"request_id,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}// internal/runtime/events.go (extend existing)
const (
EventTypeActivityToolCallStarted EventType = "activity.tool_call.started"
EventTypeActivityToolCallCompleted EventType = "activity.tool_call.completed"
EventTypeActivityPolicyDecision EventType = "activity.policy_decision"
EventTypeActivityQuarantineChange EventType = "activity.quarantine_change"
)// internal/runtime/activity_service.go
type ActivityService struct {
storage *storage.Manager
logger *zap.Logger
}
func (s *ActivityService) Record(record *storage.ActivityRecord) error {
return s.storage.SaveActivity(record)
}
func (s *ActivityService) HandleActivityEvent(evt Event) {
// Convert event to record and store
record := eventToActivityRecord(evt)
if err := s.Record(record); err != nil {
s.logger.Error("Failed to record activity", zap.Error(err))
}
}// internal/httpapi/activity.go
func (s *Server) handleListActivity(w http.ResponseWriter, r *http.Request) {
// Parse filters from query params
filters := parseActivityFilters(r)
// Query storage
activities, total, err := s.controller.ListActivities(filters)
if err != nil {
s.writeError(w, http.StatusInternalServerError, err.Error())
return
}
// Return response
s.writeSuccess(w, contracts.ActivityListResponse{
Activities: activities,
Total: total,
Limit: filters.Limit,
Offset: filters.Offset,
})
}# Get recent activities
curl -H "X-API-Key: your-key" \
"http://127.0.0.1:8080/api/v1/activity"
# Filter by server
curl -H "X-API-Key: your-key" \
"http://127.0.0.1:8080/api/v1/activity?server=github"
# Filter by type and status
curl -H "X-API-Key: your-key" \
"http://127.0.0.1:8080/api/v1/activity?type=tool_call&status=error"
# Pagination
curl -H "X-API-Key: your-key" \
"http://127.0.0.1:8080/api/v1/activity?limit=20&offset=40"curl -H "X-API-Key: your-key" \
"http://127.0.0.1:8080/api/v1/activity/01HQWX1Y2Z3A4B5C6D7E8F9G0H"# JSON Lines format
curl -H "X-API-Key: your-key" \
"http://127.0.0.1:8080/api/v1/activity/export?format=json" \
-o activity.jsonl
# CSV format
curl -H "X-API-Key: your-key" \
"http://127.0.0.1:8080/api/v1/activity/export?format=csv" \
-o activity.csv# Connect to SSE stream (activities appear as they happen)
curl -H "X-API-Key: your-key" \
"http://127.0.0.1:8080/events"
# Events you'll see:
# event: activity.tool_call.started
# data: {"activity_id":"01HQ...","server":"github","tool":"create_issue"}
#
# event: activity.tool_call.completed
# data: {"activity_id":"01HQ...","status":"success","duration_ms":234}# Run activity storage tests
go test -v ./internal/storage -run TestActivity
# Run activity API tests
go test -v ./internal/httpapi -run TestActivity# Start mcpproxy
./mcpproxy serve &
# Make a tool call
curl -X POST -H "X-API-Key: your-key" \
-H "Content-Type: application/json" \
-d '{"server":"everything","tool":"echo","arguments":{"message":"test"}}' \
"http://127.0.0.1:8080/api/v1/call-tool"
# Verify activity was recorded
curl -H "X-API-Key: your-key" \
"http://127.0.0.1:8080/api/v1/activity?limit=1"Add to ~/.mcpproxy/mcp_config.json:
{
"activity_retention_days": 90,
"activity_max_records": 100000,
"activity_max_response_size": 65536
}- Check event bus subscription is active
- Verify storage manager is initialized
- Check logs for "Failed to record activity" errors
- Verify SSE connection is established (look for heartbeat)
- Check that activity events are being emitted (debug log)
- Ensure activity event types are in the SSE handler switch
- Add filters to reduce dataset size
- Check server timeout configuration
- Consider streaming the response (already implemented)