Feature: 016-activity-log-backend Date: 2025-12-26
Primary entity for storing all activity events.
| Field | Type | Required | Description |
|---|---|---|---|
id |
string | Yes | Unique identifier (ULID format) |
type |
ActivityType | Yes | Type of activity |
server_name |
string | No | Name of upstream MCP server (if applicable) |
tool_name |
string | No | Name of tool called (if applicable) |
arguments |
map[string]any | No | Tool call arguments (JSON) |
response |
string | No | Tool response (potentially truncated) |
response_truncated |
bool | No | True if response was truncated |
status |
string | Yes | Result status: "success", "error", "blocked" |
error_message |
string | No | Error details if status is "error" |
duration_ms |
int64 | No | Execution duration in milliseconds |
timestamp |
time.Time | Yes | When activity occurred |
session_id |
string | No | MCP session ID for correlation |
request_id |
string | No | HTTP request ID for correlation |
metadata |
map[string]any | No | Additional context-specific data |
Storage Key: {timestamp_ns}_{id} for reverse-chronological ordering
Bucket: activity_records
| Value | Description |
|---|---|
tool_call |
Tool execution (start and completion) |
policy_decision |
Policy blocked a tool call |
quarantine_change |
Server quarantine state changed |
server_change |
Server added, removed, or configuration changed |
Query parameters for filtering activity records.
| Field | Type | Description |
|---|---|---|
type |
string | Filter by activity type |
server |
string | Filter by server name |
tool |
string | Filter by tool name |
session_id |
string | Filter by MCP session |
status |
string | Filter by status (success/error/blocked) |
start_time |
time.Time | Activities after this time |
end_time |
time.Time | Activities before this time |
limit |
int | Max records to return (default 50, max 100) |
offset |
int | Pagination offset |
Real-time event pushed to connected clients.
| Field | Type | Description |
|---|---|---|
event_type |
string | SSE event name |
activity_id |
string | Reference to ActivityRecord |
timestamp |
int64 | Unix timestamp |
payload |
map[string]any | Event-specific data |
Event Types:
activity.tool_call.started- Tool execution beganactivity.tool_call.completed- Tool execution finishedactivity.policy_decision- Policy blocked a callactivity.quarantine_change- Quarantine state changed
┌─────────────────┐
│ MCPSession │
│ (existing) │
└────────┬────────┘
│ 1:N
▼
┌─────────────────┐
│ ActivityRecord │◄──────────────┐
└────────┬────────┘ │
│ │
│ type="tool_call" │ type="quarantine_change"
▼ │
┌─────────────────┐ ┌────────┴────────┐
│ ToolCallRecord │ │ ServerConfig │
│ (existing) │ │ (existing) │
└─────────────────┘ └─────────────────┘
| Rule | Validation |
|---|---|
| ID format | Must be valid ULID (26 chars, Crockford Base32) |
| Type | Must be one of defined ActivityType values |
| Status | Must be "success", "error", or "blocked" |
| Timestamp | Must not be zero, must not be in future |
| Server name | If type is tool_call, must not be empty |
| Tool name | If type is tool_call, must not be empty |
| Rule | Validation |
|---|---|
| Limit | 1-100, default 50 |
| Offset | >= 0 |
| Time range | start_time must be before end_time |
EmitActivity(started)
│
┌──────────────────────▼──────────────────────┐
│ STARTED │
│ status: "pending" │
│ timestamp: now │
└──────────────────────┬───────────────────────┘
│
┌────────────┼────────────┐
│ │ │
▼ ▼ ▼
┌────────┐ ┌──────────┐ ┌─────────┐
│SUCCESS │ │ ERROR │ │ BLOCKED │
│ │ │ │ │(policy) │
└────────┘ └──────────┘ └─────────┘
│ │ │
└────────────┼────────────┘
│
▼
EmitActivity(completed)
const (
ActivityRecordsBucket = "activity_records" // Primary storage
)Key: {timestamp_ns}_{ulid}
"17035123456789012345_01HQWX1Y2Z3A4B5C6D7E8F9G0H"
Value: JSON-encoded ActivityRecord
BBolt doesn't support secondary indexes. For efficient filtering:
- Time queries: Natural key ordering supports efficient range scans
- Server/tool filters: Post-fetch filtering (acceptable for <100K records)
- Session correlation: Store session_id in record, filter in memory
For future scale (>100K records), consider:
- Separate bucket per server
- Secondary index buckets (server→activity_ids, session→activity_ids)
New fields in mcp_config.json:
{
"activity_retention_days": 90,
"activity_max_records": 100000,
"activity_max_response_size": 65536,
"activity_cleanup_interval_hours": 1
}| Field | Type | Default | Description |
|---|---|---|---|
activity_retention_days |
int | 90 | Max age before pruning |
activity_max_records |
int | 100000 | Max records before pruning |
activity_max_response_size |
int | 65536 | Response truncation limit (bytes) |
activity_cleanup_interval_hours |
int | 1 | Background cleanup interval |