Trovella Wiki

Middleware and Logging

How tool call logging middleware wraps every MCP tool invocation, the metadata capture strategy, and the audit log requirement.

The MCP server has two logging systems that serve different purposes: tool call logging captures every MCP tool invocation for operational observability, and audit logging captures plan/step mutations for the immutable business event trail.

Tool Call Logging Middleware

The withToolCallLogging middleware in packages/mcp/src/middleware/tool-call-logger.ts transparently logs every tool invocation (except ping) to the mcp_tool_call_log table. It is applied before any tools are registered in server.ts, so all subsequently registered tools are automatically wrapped.

How It Works

The middleware monkey-patches server.registerTool to wrap each tool's callback function:

Original flow:      registerTool(name, config, callback)
Patched flow:       registerTool(name, config, wrappedCallback)
                                                   |
                                                   |- Record start time
                                                   |- Call original callback
                                                   |- Record duration
                                                   |- Fire-and-forget: write log row
                                                   |- Return original result

Key design decisions:

  • Fire-and-forget -- the log write is wrapped in a void async IIFE. If logging fails, the tool response is unaffected. A logger.warn is emitted on failure but the error is swallowed.
  • Ping excluded -- ping is a health check that would generate noise. The middleware skips it by checking the tool name.
  • Error detection -- the middleware catches both thrown exceptions and MCP-style error responses ({ isError: true }).

Metadata Capture

The middleware captures both request and response metadata, with intelligent truncation:

Request metadata -- the tool's input arguments, with long text fields truncated:

  • Fields in the LONG_TEXT_FIELDS set (e.g., content, instructions, sourceText, result, report) are replaced with [N chars] if they exceed 500 characters
  • Nested objects are traversed up to 5 levels deep
  • Arrays longer than 10 items are truncated with a ...[N more] suffix

Response metadata -- parsed from the JSON text content of the tool's response:

  • Successful responses have their fields extracted (with the same truncation rules)
  • Error responses include isError: true
  • Non-JSON responses store truncated text

Reference extraction -- planId, stepId, sessionId, and skillExecutionId are extracted from the tool arguments into dedicated columns for efficient querying.

Log Row Schema

Each row in mcp_tool_call_log contains:

ColumnTypePurpose
idTEXT (PK)UUID
organizationIdTEXTTenant isolation
userIdTEXTAuthenticated user
toolNameTEXTMCP tool name (e.g., submit_step_result)
planIdTEXTExtracted from args (nullable)
stepIdTEXTExtracted from args (nullable)
skillExecutionIdTEXTExtracted from executionId arg (nullable)
claudeCodeSessionIdTEXTExtracted from sessionId arg (nullable)
requestMetadataJSONBTruncated tool arguments
responseMetadataJSONBTruncated tool response
isErrorBOOLEANWhether the call failed
durationMsINTEGERWall-clock execution time
createdAtTIMESTAMPWhen the call occurred

Plan Audit Log

The audit log in packages/mcp/src/tools/helpers/audit-log.ts serves a different purpose from tool call logging. It records business events -- plan created, step started, step completed, user reviewed -- as an immutable append-only trail in the plan_audit_log table.

When to Write Audit Entries

Every tool that mutates plan or step state must call writeAuditLog() inside the same database transaction, before commit. This ensures the audit entry and the state change are atomic -- either both succeed or both roll back.

Tools that write audit entries:

ToolEvent Type
create_research_planplan_modified (action: created)
modify_planplan_modified (action varies by action type)
get_next_stepstep_started
submit_step_resultstep_completed
request_user_reviewuser_reviewed (action: review_requested)
submit_user_decisionuser_reviewed (decision details)
get_research_contextsession_resumed (when sessionId provided)

Tools that do not write audit entries (read-only or non-plan operations):

  • ping, get_plan_status, get_step_context, list_active_plans
  • store_research, search_sources, extract_data, store_research_output
  • submit_subagent_report, log_skill_execution, submit_research_feedback

Audit Event Types

Event TypeMeaning
step_startedA step transitioned to in_progress
step_completedA step's result was submitted
step_failedA step was explicitly failed
plan_modifiedPlan structure changed (created, steps added/removed/reordered/updated)
user_reviewedA review was requested or a user decision was recorded
session_resumedA new Claude Code session loaded an existing plan's context
skill_startedA skill execution began
skill_completedA skill execution finished

The writeAuditLog Function

interface AuditLogEntry {
  planId: string;
  organizationId: string;
  stepId?: string | null;
  eventType: AuditEventType;
  eventData?: Record<string, unknown>;
  sessionId?: string | null;
}

async function writeAuditLog(tx: Database, entry: AuditLogEntry): Promise<void> {
  await tx.insert(planAuditLog).values({
    id: randomUUID(),
    planId: entry.planId,
    organizationId: entry.organizationId,
    stepId: entry.stepId ?? null,
    eventType: entry.eventType,
    eventData: entry.eventData ?? null,
    claudeCodeSessionId: entry.sessionId ?? null,
  });
}

The function receives the transaction (tx) rather than using the global db client, guaranteeing it runs within the caller's tenant-scoped transaction.

Two Logging Systems Compared

AspectTool Call LogPlan Audit Log
Tablemcp_tool_call_logplan_audit_log
ScopeEvery tool invocationPlan/step mutations only
TimingFire-and-forget (async, after response)Inside transaction (sync, before commit)
Failure modeSilent -- tool response unaffectedTransaction rolls back -- mutation undone
PurposeOperational observability, debuggingBusiness event trail, compliance
IncludesRequest/response metadata, durationEvent type, event data, session

On this page