Skip to content

3rd Party MCP - Audit Source Column + Request Context Propagation #7562

Description

@cstns

Summary

Add a source column to the AuditLog model and propagate request source information through @fastify/request-context so audit loggers can record how an action was triggered.

Why

MCP platform tools use app.inject() to call existing HTTP routes. Audit logging happens inside those route handlers. From the route's perspective, an MCP-originated request looks identical to a regular API request. Without explicit signaling, administrators have no way to tell "user did this in the UI" from "3rd party agent did this via MCP" from "first-party expert agent did this."

A queryable database column (rather than burying it in the JSON body) lets administrators filter audit logs by source directly, which is the primary use case: "show me everything an MCP agent did in the last 24 hours."

@fastify/request-context is the carrier because it's already used for PAT metadata (#7411). The audit loggers can read from it without changing their function signatures or touching every route handler that calls them. The source is set once (during auth / header processing) and available everywhere downstream.

What to do

Database migration

  • Add source column to AuditLogs table: STRING, nullable, default null
  • Null represents the legacy/UI path (cookie session, no PAT, no MCP header)

Source detection and propagation

  • After verifySession, detect the request source and store it in @fastify/request-context:
    • Cookie session (no PAT): source remains null (or ui if we want to be explicit)
    • PAT present, no MCP header: api
    • MCP custom header X-FF-Audit-Source: mcp: mcp (3rd party agent)
    • MCP custom header X-FF-Audit-Source: mcp:expert: mcp:expert (first-party agent)
  • Also store the token identifier from X-FF-Audit-Token header when present, for traceability to the specific PAT/integration

Audit logger updates

  • Update the centralized audit logging functions (platformLog, userLog, teamLog, projectLog, applicationLog, deviceLog in forge/db/controllers/AuditLog.js) to read source from @fastify/request-context and pass it to the model
  • Update the AuditLog view formatter to include source in the output
  • Update the triggerObject() in formatters.js to include the token identifier when available

Tests

  • MCP-originated action (via app.inject() with custom headers) records source: 'mcp'
  • First-party expert action records source: 'mcp:expert'
  • Direct PAT API call records source: 'api'
  • Cookie session action records source: null (or ui)
  • Token identifier is included in the audit body when present
  • Existing audit entries (before migration) have null source
  • Audit log API returns the source field

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions