Skip to content

Latest commit

 

History

History
586 lines (452 loc) · 22.9 KB

activity-log.md

File metadata and controls

586 lines (452 loc) · 22.9 KB
id activity-log
title Activity Log
sidebar_label Activity Log
sidebar_position 7
description Track and audit AI agent tool calls with the Activity Log
keywords
activity
logging
audit
observability
compliance

Activity Log

MCPProxy records AI agent tool calls, policy decisions, and system events in a local activity log. This supports debugging and after-the-fact inspection. Records are kept subject to configurable retention (age, record count and total size) and a per-record response size cap (see Configuration), so the log is not a complete or tamper-proof audit trail and is not a regulatory compliance certification.

What Gets Logged

The activity log captures:

Event Type Description
tool_call Every tool call made through MCPProxy
system_start MCPProxy server startup events
system_stop MCPProxy server shutdown events
internal_tool_call Internal proxy tool calls (retrieve_tools, call_tool_*, code_execution, etc.)
config_change Configuration changes (server added/removed/updated)
policy_decision Tool calls blocked by policy rules
quarantine_change Server quarantine/unquarantine events
server_change Server enable/disable/restart events
credential_broker Per-user oauth_connect consent/callback outcomes (connect is the only action ever recorded; the credential is stored, never injected) — server edition only

System Lifecycle Events

System lifecycle events track when MCPProxy starts and stops:

{
  "id": "01JFXYZ123DEF",
  "type": "system_start",
  "status": "success",
  "timestamp": "2025-01-15T10:00:00Z",
  "metadata": {
    "version": "v0.5.0",
    "listen_address": "127.0.0.1:8080",
    "startup_duration_ms": 150,
    "config_path": "/Users/user/.mcpproxy/mcp_config.json"
  }
}

Internal Tool Call Events

Internal tool calls log when internal proxy tools are used:

{
  "id": "01JFXYZ123GHI",
  "type": "internal_tool_call",
  "status": "success",
  "duration_ms": 45,
  "timestamp": "2025-01-15T10:05:00Z",
  "metadata": {
    "internal_tool_name": "call_tool_read",
    "target_server": "github-server",
    "target_tool": "get_user",
    "tool_variant": "call_tool_read",
    "intent": {
      "operation_type": "read",
      "data_sensitivity": "public"
    }
  }
}

:::note Duplicate Filtering By default, all call_tool_* internal tool calls (call_tool_read, call_tool_write, call_tool_destructive) are excluded from activity listings — every dispatch (successful, failed, or shed) has a corresponding upstream tool_call or rejection record carrying the same request_id, so showing both would double-count the call.

To include the call_tool_* records anyway, use include_call_tool=true in the API query parameter. :::

Config Change Events

Configuration changes are logged for audit trails:

{
  "id": "01JFXYZ123JKL",
  "type": "config_change",
  "server_name": "github-server",
  "status": "success",
  "timestamp": "2025-01-15T10:10:00Z",
  "metadata": {
    "action": "server_added",
    "affected_entity": "github-server",
    "source": "mcp",
    "new_values": {
      "name": "github-server",
      "url": "https://api.github.com/mcp"
    }
  }
}

Tool Call Records

Each tool call record includes:

{
  "id": "01JFXYZ123ABC",
  "type": "tool_call",
  "server_name": "github-server",
  "tool_name": "create_issue",
  "tool_variant": "call_tool_write",
  "arguments": {"title": "Bug report", "body": "..."},
  "response": "Issue #123 created",
  "status": "success",
  "duration_ms": 245,
  "timestamp": "2025-01-15T10:30:00Z",
  "session_id": "mcp-session-abc123",
  "request_id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890",
  "intent": {
    "operation_type": "write",
    "data_sensitivity": "internal",
    "reason": "Creating bug report per user request"
  }
}

Sub-calls made by code_execution

Every upstream tool call a sandboxed code_execution script makes is recorded as a first-class tool_call record with its own request_id and a parent_id equal to the parent code_execution record's request_id (source is internal; a policy-refused sub-call is recorded with status blocked; when the refusal comes from the caller's profile it also carries block_reason: profile_tier, profile_rule or profile_unannotated; a sub-call to a server outside the caller's profile carries profile_server_scope. The refusal text the script sees is unchanged, and a legacy token refused only by its own server list carries no block_reason). Filter with parent_id=<parent request_id> to list a script's sub-calls, or request_id=<child's parent_id> to find the parent — the Web UI drawer, the macOS Activity window, and mcpproxy activity list --parent-id all expose the same navigation (see the "Drill into a code_execution" workflow in the CLI activity commands reference).

Failure classification: error_class and fault_domain

A failed tool call tells you what failed and whose fault it is. Every tool_call record with status: "error" (and a limiter shed or profile refusal) carries three optional fields, in the REST API, the SSE activity.tool_call.completed payload and mcpproxy activity list|show -o json|yaml:

Field Meaning
error_class What failed, one of the classes below
fault_domain upstream (the server or the network to it), proxy (mcpproxy refused or failed) or client (the caller sent something invalid or went away)
upstream_http_status The HTTP status of the upstream response, when known (non-2xx only)
error_class Typical fault_domain When
tool_error upstream The tool answered isError: true. The record keeps status: "error"; this class tells it apart from a transport failure.
jsonrpc upstream The upstream answered with a JSON-RPC error, including server-specific codes such as -32001.
http upstream The upstream answered with a non-2xx HTTP status (for example 502 with an HTML body); upstream_http_status is set.
network upstream Connection refused or reset, DNS failure, EOF, closed transport, or the server is not connected.
timeout upstream or proxy A deadline passed: upstream if the request had been sent, proxy if it expired before dispatch.
session_terminated upstream The upstream no longer knows the MCP session (HTTP 404).
auth upstream HTTP 401/403 or an OAuth authorization-required error.
proxy_policy proxy or client mcpproxy refused the call: a profile policy, the concurrency limiter, or a stale connection generation (proxy); arguments that failed pre-dispatch validation (client).
proxy_internal proxy A failure inside mcpproxy that no other class explains.
cancelled client The caller cancelled the request. Not counted as an upstream fault.

The fields are absent on successful calls and on records written before this feature, which decode and display exactly as before. Filter with error_class=<class> and fault_domain=<domain> (REST), or --error-class and --fault-domain (CLI). The Spec 107 audit line keeps its own error_class vocabulary; it is derived from this classification so the two never disagree.

Intent Tracking

Every tool call includes intent information for security auditing:

Field Description
tool_variant Which tool was used: call_tool_read, call_tool_write, call_tool_destructive
intent.operation_type Agent's declared intent: read, write, destructive
intent.data_sensitivity Data classification: public, internal, private, unknown
intent.reason Agent's explanation for the operation

Sensitive Data Detection

Activity records automatically include sensitive data detection metadata when MCPProxy detects potentially sensitive information in tool arguments or responses. This helps identify data handling patterns. Detection only flags records; it does not block the call by default (see what detection does and does not do).

{
  "id": "01JFXYZ123ABC",
  "type": "tool_call",
  "server_name": "github-server",
  "tool_name": "create_issue",
  "status": "success",
  "metadata": {
    "sensitive_data_detection": {
      "detected": true,
      "categories": ["api_key", "email"],
      "argument_detections": [
        {
          "field": "body",
          "category": "api_key",
          "confidence": 0.95
        }
      ],
      "response_detections": [
        {
          "field": "author.email",
          "category": "email",
          "confidence": 0.99
        }
      ]
    }
  }
}
Field Description
detected Whether any sensitive data was detected
categories List of detected sensitive data categories
argument_detections Detections in tool call arguments
response_detections Detections in tool call responses

Filter activity by sensitive data:

# Show only activity with detected sensitive data
mcpproxy activity list --has-sensitive-data

# Filter by specific category
mcpproxy activity list --sensitive-category api_key

# REST API
curl -H "X-API-Key: $KEY" "http://127.0.0.1:8080/api/v1/activity?has_sensitive_data=true"
curl -H "X-API-Key: $KEY" "http://127.0.0.1:8080/api/v1/activity?sensitive_category=api_key"

See Sensitive Data Detection for details on detection categories, configuration options, and compliance use cases.

Filter by intent type:

# Show only destructive operations
mcpproxy activity list --intent-type destructive

# REST API
curl -H "X-API-Key: $KEY" "http://127.0.0.1:8080/api/v1/activity?intent_type=destructive"

See Intent Declaration for details on the intent-based permission system.

Request ID Correlation

Every activity record includes a request_id that links to the HTTP request that triggered it. This is useful for:

  • Error debugging: When an API error occurs, the error response includes the request_id. Use it to find related activity:
# Error response includes request_id
# { "error": "tool call failed", "request_id": "abc123..." }

# Find activity for that request
mcpproxy activity list --request-id abc123...
  • Request tracing: Track all tool calls made during a single API request.

  • Log correlation: The same request_id appears in server logs, enabling end-to-end request tracing.

Filter by request ID:

# CLI
mcpproxy activity list --request-id a1b2c3d4-e5f6-7890-abcd-ef1234567890

# REST API
curl -H "X-API-Key: $KEY" "http://127.0.0.1:8080/api/v1/activity?request_id=a1b2c3d4-e5f6-7890-abcd-ef1234567890"

CLI Commands

MCPProxy provides dedicated CLI commands for activity log access. See the full Activity Commands Reference for details.

Quick Examples

# List recent activity
mcpproxy activity list

# List last 10 tool call errors
mcpproxy activity list --type tool_call --status error --limit 10

# Watch activity in real-time
mcpproxy activity watch

# Show activity statistics
mcpproxy activity summary --period 24h

# View specific activity details
mcpproxy activity show 01JFXYZ123ABC

# Export for compliance
mcpproxy activity export --output audit.jsonl

Available Commands

Command Description
activity list List activity records with filtering and pagination
activity watch Watch real-time activity stream via SSE
activity show <id> Show full details of a specific activity
activity summary Show aggregated statistics for a time period
activity export Export activity records to file (JSON/CSV)

All commands support --output json, --output yaml, or --json for machine-readable output.


REST API

List Activity

GET /api/v1/activity

Query Parameters:

Parameter Type Description
type string Filter by type (comma-separated for multiple): tool_call, system_start, system_stop, internal_tool_call, config_change, policy_decision, quarantine_change, server_change, credential_broker (server edition; connect-flow outcomes only)
server string Filter by server name
tool string Filter by tool name
session_id string Filter by MCP session ID
status string Filter by status: success, error, blocked
start_time string Filter after this time (RFC3339)
end_time string Filter before this time (RFC3339)
limit integer Max records (1-100, default: 50)
offset integer Pagination offset (default: 0)
include_call_tool boolean Include call_tool_* internal tool calls (default: false). Excluded by default because every dispatch has a paired tool_call (or rejection) record with the same request_id.
parent_id string Return only the sub-calls one code_execution issued (value = the parent record's request_id). Child→parent is the reverse lookup: request_id=<child's parent_id>.
error_class string Return only failed calls of this class: network, timeout, http, jsonrpc, tool_error, session_terminated, auth, proxy_policy, proxy_internal, cancelled. See Failure classification.
fault_domain string Return only failed calls with this fault domain: upstream, proxy, client
exclude_payloads boolean Omit the bulky fields — arguments, response — and narrow metadata to a contextual whitelist (default: false). See below.

exclude_payloads and the contextual metadata whitelist

exclude_payloads=true is a projection, not a filter: it never changes which records match, only how much of each record is serialized. It exists for clients that poll frequently and render summary fields only (the macOS tray glance polls the newest 100 records on every menu open). Measured against a real log those 100 records are ~848 KB whole and ~30 KB projected.

The projection drops arguments and response entirely, and keeps only these metadata keys:

Kept key Why
intent.reason The caller's stated reason, rendered as the row subtitle
intent.operation_type read / write / destructive
decision blocked, warned, … for policy_decision records
reason Why a policy decision blocked or warned
client_name Which MCP client made the call
client_version Its version

Everything else in metadata — sensitive-data detection payloads, toon renderings, classifier scores, raw intent objects — is dropped. Whitelisted keys that a record does not have are simply omitted, and a record whose metadata is entirely non-whitelisted serializes with no metadata object at all.

Every kept key is kept only when its value is a string. A whitelisted key is not a promise about its value: a producer that writes a structured error under reason would otherwise smuggle that whole payload through the projection, so non-string values are dropped like any other non-whitelisted content.

Two things survive the projection deliberately:

  • has_sensitive_data is derived from metadata before it is narrowed, so the flag outlives its source.
  • request_id is a top-level field, not metadata, so correlation still works on projected responses.

Fetch the full record with GET /api/v1/activity/{id} when a client needs the arguments, the response, or any non-whitelisted metadata.

Example:

# List recent tool calls
curl -H "X-API-Key: $KEY" "http://127.0.0.1:8080/api/v1/activity?type=tool_call&limit=10"

# Filter by multiple types (comma-separated)
curl -H "X-API-Key: $KEY" "http://127.0.0.1:8080/api/v1/activity?type=tool_call,internal_tool_call,config_change"

# List system lifecycle events
curl -H "X-API-Key: $KEY" "http://127.0.0.1:8080/api/v1/activity?type=system_start,system_stop"

# Filter by server
curl -H "X-API-Key: $KEY" "http://127.0.0.1:8080/api/v1/activity?server=github-server"

# Filter by time range
curl -H "X-API-Key: $KEY" "http://127.0.0.1:8080/api/v1/activity?start_time=2025-01-15T00:00:00Z"

# Summary-only poll: no arguments/response, metadata narrowed to the whitelist
curl -H "X-API-Key: $KEY" "http://127.0.0.1:8080/api/v1/activity?type=tool_call,internal_tool_call,policy_decision&limit=100&exclude_payloads=true"

Response:

{
  "success": true,
  "data": {
    "activities": [
      {
        "id": "01JFXYZ123ABC",
        "type": "tool_call",
        "server_name": "github-server",
        "tool_name": "create_issue",
        "status": "success",
        "duration_ms": 245,
        "timestamp": "2025-01-15T10:30:00Z"
      }
    ],
    "total": 150,
    "limit": 50,
    "offset": 0
  }
}

Get Activity Detail

GET /api/v1/activity/{id}

Returns full details including request arguments and response data.

Export Activity

GET /api/v1/activity/export

Export activity records for compliance and auditing.

Query Parameters:

Parameter Type Description
format string Export format: json (JSON Lines) or csv
(filters) Same filters as list endpoint

Example:

# Export as JSON Lines
curl -H "X-API-Key: $KEY" "http://127.0.0.1:8080/api/v1/activity/export?format=json" > activity.jsonl

# Export as CSV
curl -H "X-API-Key: $KEY" "http://127.0.0.1:8080/api/v1/activity/export?format=csv" > activity.csv

# Export specific time range
curl -H "X-API-Key: $KEY" "http://127.0.0.1:8080/api/v1/activity/export?start_time=2025-01-01T00:00:00Z&end_time=2025-01-31T23:59:59Z"

Real-time Events

Activity events are streamed via SSE for real-time monitoring:

curl -N "http://127.0.0.1:8080/events?apikey=$KEY"

Events:

Event Description
activity.tool_call.started Tool call initiated
activity.tool_call.completed Tool call finished (success or error)
activity.policy_decision Tool call blocked by policy

Example Event:

event: activity.tool_call.completed
data: {"id":"01JFXYZ123ABC","server":"github-server","tool":"create_issue","status":"success","duration_ms":245}

Configuration

Activity logging is enabled by default. Configure via mcp_config.json:

{
  "activity_retention_days": 90,
  "activity_max_records": 100000,
  "activity_max_size_mb": 256,
  "activity_max_response_size": 65536,
  "activity_cleanup_interval_min": 60
}
Setting Default Description
activity_retention_days 90 Days to retain activity records
activity_max_records 100000 Maximum records before pruning oldest
activity_max_size_mb 256 Maximum total activity-log size in MB before pruning oldest (0 disables). Runs alongside the age and count caps to bound config.db growth when records carry large payloads. An explicit 0 now survives a config save — before #1175 it was silently deleted on the next write and the 256MB cap came back.
activity_max_response_size 65536 Max response text stored per record (bytes). Applies to tool_call, internal_tool_call and prompt_get; 0 or absent falls back to 65536 and it cannot be disabled. Read at startup only, like its retention siblings. Truncated text keeps a ...[truncated] suffix. Already-stored records are not rewritten, and (as with pruning) the BBolt file does not shrink on disk. Sensitive-data detection still scans the untruncated response, under its own sensitive_data_detection.max_payload_size_kb cap.
activity_cleanup_interval_min 60 Background cleanup interval (minutes)

Why the size cap? The age and count caps alone do not bound disk: with large per-record payloads the log can reach hundreds of MB while still under 100k records / 90 days. activity_max_size_mb removes the oldest records (always keeping the newest) until the log is within the byte budget. Note: pruning frees pages for reuse but does not shrink the database file on disk (BBolt does not return freed pages to the OS).

Tool-call history

The activity log is not the only per-call store. GET /api/v1/tool-calls is served from separate per-server buckets (server_<id>_tool_calls) that hold a recent debugging window: the full arguments and the upstream result, per server. On one deployment these held ~432MB of a 940MB config.db because nothing bounded them at all (#1176).

{
  "tool_call_max_response_size": 65536,
  "tool_call_max_records_per_server": 1000
}
Setting Default Description
tool_call_max_response_size 65536 Cap on the marshalled response stored per tool-call record, in bytes. Over the cap the stored response is replaced by {"truncated": true, "original_bytes": N, "preview": "…"} and the record carries response_truncated: true with response_bytes: N. The caller still received the response whole — only the stored copy is shortened.
tool_call_max_records_per_server 1000 Calls retained per server. The oldest are evicted in the same transaction as the write, so the bucket can never exceed the cap.

A non-positive value means "use the default", not "disable" — this store has no off switch. Removing a server now drops its call history with it, and histories belonging to servers that are no longer configured are swept on startup (the synthetic code_execution history is never swept).

Reclaiming the space. All of the above bounds future growth. BBolt does not return freed pages to the operating system, so an already-large config.db stays large. Use mcpproxy db stats to see how much is reclaimable and mcpproxy db compact — with mcpproxy stopped — to shrink the file. See docs/cli/db-commands.md.

Use Cases

Debugging Tool Calls

View recent tool calls to debug issues:

curl -H "X-API-Key: $KEY" \
  "http://127.0.0.1:8080/api/v1/activity?type=tool_call&status=error&limit=10"

Compliance Auditing

Export activity for compliance review:

curl -H "X-API-Key: $KEY" \
  "http://127.0.0.1:8080/api/v1/activity/export?format=csv&start_time=2025-01-01T00:00:00Z" \
  > audit-q1-2025.csv

Session Analysis

Track all activity for a specific AI session:

curl -H "X-API-Key: $KEY" \
  "http://127.0.0.1:8080/api/v1/activity?session_id=mcp-session-abc123"

Real-time Monitoring

Monitor tool calls in real-time:

curl -N "http://127.0.0.1:8080/events?apikey=$KEY" | grep "activity.tool_call"

Storage

Activity records are stored in BBolt database at ~/.mcpproxy/config.db. The background cleanup process automatically prunes old records based on retention settings.

:::tip Performance Activity logging is non-blocking and uses an event-driven architecture to minimize impact on tool call latency. :::