Skip to content

feat(web): add MCP and API key usage tracking to analytics#948

Closed
msukkari wants to merge 3 commits intomainfrom
msukkari/mcp-analytics
Closed

feat(web): add MCP and API key usage tracking to analytics#948
msukkari wants to merge 3 commits intomainfrom
msukkari/mcp-analytics

Conversation

@msukkari
Copy link
Contributor

@msukkari msukkari commented Feb 26, 2026

Summary

Analytics: MCP & API usage tracking

  • Move audit event creation from client-side search bar to service functions (search, streamSearch, listRepos, getFileSource, getTree)
  • Add source metadata to audit events to distinguish MCP requests from other API calls
  • Extend analytics SQL query to include new audit actions and display MCP request and API request counts
  • Add two new charts to analytics dashboard: MCP Requests and API Requests

Audit log retention policy

  • Add SOURCEBOT_EE_AUDIT_RETENTION_DAYS environment variable (default: 180) to configure how long audit logs are kept
  • Add AuditLogPruner background job that runs daily and prunes audit records older than the configured retention period, deleting in batches of 10K to avoid long-running transactions
  • Set to 0 to disable pruning and retain logs indefinitely

Analytics UI: retention info

  • Surface the configured retention period and oldest audit record date in the analytics page header
  • Wrap the analytics response in an object ({ rows, retentionDays, oldestRecordDate }) instead of returning a flat array

Documentation updates

  • Audit logs docs: add retention policy section, update audit action types table (removed 4 stale actions, added 11 missing actions), add chat to targetType enum, add source to metadata schema
  • Environment variables docs: add SOURCEBOT_EE_AUDIT_RETENTION_DAYS entry
  • Sizing guide: add audit log storage section with enterprise callout, per-event storage estimate (~350 bytes), projected storage table for various team sizes, and retention policy guidance

Mock data script

  • Update inject-audit-data.ts with 5 mixed-usage user profiles (web-only, web+MCP hybrid, MCP-heavy, API-only, web+API hybrid) with randomized weights
  • Add all new audit actions and source metadata for MCP/API users

Screenshots

image

Retention policy and oldest audit log date displayed in analytics header:

Retention info in analytics header

Test plan

  • Verify web UI searches still fire audit events and appear in analytics
  • Verify MCP tool invocations fire audit events with source='mcp' and appear in MCP Requests chart
  • Verify non-MCP API key usage fires audit events and appears in API Requests chart
  • Verify API users count as active users in analytics
  • Verify retention period and "data since" date display correctly in analytics header
  • Verify audit log pruner runs daily and correctly deletes records older than the configured retention period
  • Verify setting SOURCEBOT_EE_AUDIT_RETENTION_DAYS=0 disables pruning

Linear: SOU-579

🤖 Generated with Claude Code

Summary by CodeRabbit

Release Notes

  • New Features

    • Analytics dashboard now displays separate charts for MCP requests and API requests.
    • Analytics dashboard now shows data retention period and oldest record date.
    • Audit log retention is now configurable with a default of 180 days.
  • Documentation

    • Updated audit logs configuration guide with retention policy details.
    • Added audit log storage sizing estimates to deployment guide.
    • Documented SOURCEBOT_EE_AUDIT_RETENTION_DAYS environment variable.

Move audit event creation from client-side to service functions for search, repos, file source, and file tree endpoints. Add source metadata to distinguish MCP requests from other API calls. Extend analytics SQL to include new actions and display MCP request and API request counts on the analytics dashboard.

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@github-actions

This comment has been minimized.

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 26, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 6726cf2 and 3af7c41.

📒 Files selected for processing (10)
  • docs/docs/configuration/audit-logs.mdx
  • docs/docs/configuration/environment-variables.mdx
  • docs/docs/deployment/sizing-guide.mdx
  • packages/backend/src/ee/auditLogPruner.ts
  • packages/backend/src/index.ts
  • packages/db/tools/scripts/inject-audit-data.ts
  • packages/shared/src/env.server.ts
  • packages/web/src/ee/features/analytics/actions.ts
  • packages/web/src/ee/features/analytics/analyticsContent.tsx
  • packages/web/src/ee/features/analytics/types.ts

Walkthrough

Moves audit event emission from client to server-side service functions, adds a request source field (from X-Sourcebot-Client-Source), records MCP vs API requests, exposes mcp_requests and api_requests in analytics types/queries, adds pruning for audit retention, and updates docs and data generation.

Changes

Cohort / File(s) Summary
Audit types
packages/web/src/ee/features/audit/types.ts
Add optional source to audit metadata schema.
Analytics types & logic
packages/web/src/ee/features/analytics/types.ts, packages/web/src/ee/features/analytics/actions.ts, packages/web/src/ee/features/analytics/analyticsContent.tsx
Introduce AnalyticsRow with mcp_requests and api_requests; update query to aggregate MCP vs API counts; update UI to render two new charts and include retention/oldest-record info.
Server-side API auditing
packages/web/src/app/api/(server)/repos/listReposApi.ts, packages/web/src/features/git/getFileSourceApi.ts, packages/web/src/features/git/getTreeApi.ts, packages/web/src/features/search/searchApi.ts
Add conditional server-side audit calls (getAuditService) for user actions (user.listed_repos, user.fetched_file_source, user.fetched_file_tree, user.performed_code_search) capturing source from header; audit calls non-blocking.
Client cleanup
packages/web/src/app/[domain]/components/searchBar/searchBar.tsx
Remove client-side audit side-effect from search submit handler.
Audit retention & pruning
packages/backend/src/ee/auditLogPruner.ts, packages/backend/src/index.ts, packages/shared/src/env.server.ts
Add AuditLogPruner class, wire it into backend startup/shutdown, and add SOURCEBOT_EE_AUDIT_RETENTION_DAYS env var (default 180).
Data generation
packages/db/tools/scripts/inject-audit-data.ts
Replace audit data generator with richer, weighted user model producing web/MCP/API events and source metadata.
Docs & changelog
docs/docs/configuration/audit-logs.mdx, docs/docs/configuration/environment-variables.mdx, docs/docs/deployment/sizing-guide.mdx, CHANGELOG.md
Document retention policy, new audit action types and source metadata, environment variable, sizing guidance, and CHANGELOG entry.

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Server
  participant AuditService
  participant DB
  participant AnalyticsUI

  Client->>Server: request (includes X-Sourcebot-Client-Source header)
  Server->>AuditService: emit audit event (actor, action, target, metadata.source)
  AuditService->>DB: insert audit record
  Note right of DB: DB stores record with source and timestamp
  Server->>Client: respond with requested data
  AnalyticsUI->>Server: query analytics (rows + retention + oldestRecordDate)
  Server->>DB: aggregate audits by action + metadata.source
  DB-->>Server: aggregated rows (mcp_requests, api_requests, etc.)
  Server-->>AnalyticsUI: analytics response (rows, retentionDays, oldestRecordDate)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

Suggested labels

sourcebot-team

Suggested reviewers

  • brendan-kellam
🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main objective of the pull request: adding MCP and API key usage tracking to analytics.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch msukkari/mcp-analytics

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
packages/web/src/features/search/searchApi.ts (1)

31-40: Consider extracting duplicated audit logic.

The audit blocks in search() and streamSearch() are identical. This duplication exists across multiple files in this PR. Consider extracting a helper function to reduce repetition and centralize the audit pattern.

♻️ Suggested helper extraction (local to this file or shared)
// Could be placed in a shared audit utilities file
const auditCodeSearch = async (user: UserWithAccounts, org: { id: number }) => {
    const source = (await headers()).get('X-Sourcebot-Client-Source') ?? undefined;
    getAuditService().createAudit({
        action: 'user.performed_code_search',
        actor: { id: user.id, type: 'user' },
        target: { id: org.id.toString(), type: 'org' },
        orgId: org.id,
        metadata: { source },
    }).catch(() => {});
};

Then in both functions:

 withOptionalAuthV2(async ({ prisma, user, org }) => {
     if (user) {
-        const source = (await headers()).get('X-Sourcebot-Client-Source') ?? undefined;
-        getAuditService().createAudit({
-            action: 'user.performed_code_search',
-            actor: { id: user.id, type: 'user' },
-            target: { id: org.id.toString(), type: 'org' },
-            orgId: org.id,
-            metadata: { source },
-        }).catch(() => {});
+        await auditCodeSearch(user, org);
     }

Also applies to: 62-71

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/features/search/searchApi.ts` around lines 31 - 40, Extract
the duplicated audit logic into a helper (e.g., auditCodeSearch) and replace the
repeated blocks in search() and streamSearch() with a single call to that
helper; the helper should capture the source via (await
headers()).get('X-Sourcebot-Client-Source') ?? undefined and call
getAuditService().createAudit({ action: 'user.performed_code_search', actor: {
id: user.id, type: 'user' }, target: { id: org.id.toString(), type: 'org' },
orgId: org.id, metadata: { source } }).catch(() => {}), then invoke
auditCodeSearch(user, org) from both search() and streamSearch() to remove
duplication.
packages/web/src/ee/features/analytics/types.ts (1)

9-11: Tighten counter field validation to integer, non-negative values.

These are count metrics; enforcing .int().nonnegative() avoids silently accepting invalid payloads.

Suggested schema hardening
-  mcp_requests: z.number(),
-  api_requests: z.number(),
-  active_users: z.number(),
+  mcp_requests: z.number().int().nonnegative(),
+  api_requests: z.number().int().nonnegative(),
+  active_users: z.number().int().nonnegative(),
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/ee/features/analytics/types.ts` around lines 9 - 11, The
count fields mcp_requests, api_requests, and active_users in the analytics
schema are plain z.number() and should be restricted to integer, non-negative
values; update their validators to use .int().nonnegative() on each of those
fields (locate the schema definition in types.ts where mcp_requests,
api_requests, and active_users are declared) so the schema enforces integer
non-negative counters.
packages/web/src/ee/features/analytics/analyticsContent.tsx (1)

178-178: Avoid hardcoded skeleton count drift.

Line 178 hardcodes 6; consider deriving from a shared chart-count constant to keep loading UI in sync when charts are added/removed.

Minimal refactor
+const ANALYTICS_CHART_COUNT = 6
+
 function LoadingSkeleton() {
   return (
@@
-            {[1, 2, 3, 4, 5, 6].map((chartIndex) => (
+            {Array.from({ length: ANALYTICS_CHART_COUNT }, (_, i) => i + 1).map((chartIndex) => (
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/ee/features/analytics/analyticsContent.tsx` at line 178,
Replace the hardcoded array [1,2,3,4,5,6] used to render skeletons with a single
source-of-truth chart count constant (e.g., CHART_COUNT) exported from the
analytics/chart configuration or the component that defines the list of charts;
update the mapping to iterate based on that CHART_COUNT (e.g., Array.from({
length: CHART_COUNT })) so the skeleton count automatically stays in sync
whenever charts are added/removed (ensure you add or import CHART_COUNT and
replace occurrences in analyticsContent.tsx where the hardcoded six is used).
packages/web/src/ee/features/analytics/actions.ts (1)

87-87: Use an explicit API-source allowlist for api_requests.

Line 87 currently treats any non-null, non-mcp source as API. That can overcount if new source labels are introduced later (e.g., non-API internal clients). Prefer explicit allowed API source values.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/web/src/ee/features/analytics/actions.ts` at line 87, The COUNT(*)
FILTER that defines api_requests currently treats any non-null/non-'mcp'
metadata->>'source' as API; change this to an explicit allowlist check against
known API source values (e.g. metadata->>'source' IN ( ... )) so only intended
sources count as api_requests, update the SQL expression that produces
api_requests accordingly, and if the allowed sources are used elsewhere consider
extracting them to a shared constant or config and adjust any tests that rely on
the previous behavior.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@CHANGELOG.md`:
- Line 11: The changelog entry mixes tenses—change the phrase "Move audit events
from client-side to service functions to capture all API calls (web UI, MCP, and
non-MCP)" to past tense (e.g., "Moved audit events from client-side to service
functions to capture all API calls (web UI, MCP, and non-MCP)") so the entire
bullet reads consistently in past tense with the rest of the entry.

---

Nitpick comments:
In `@packages/web/src/ee/features/analytics/actions.ts`:
- Line 87: The COUNT(*) FILTER that defines api_requests currently treats any
non-null/non-'mcp' metadata->>'source' as API; change this to an explicit
allowlist check against known API source values (e.g. metadata->>'source' IN (
... )) so only intended sources count as api_requests, update the SQL expression
that produces api_requests accordingly, and if the allowed sources are used
elsewhere consider extracting them to a shared constant or config and adjust any
tests that rely on the previous behavior.

In `@packages/web/src/ee/features/analytics/analyticsContent.tsx`:
- Line 178: Replace the hardcoded array [1,2,3,4,5,6] used to render skeletons
with a single source-of-truth chart count constant (e.g., CHART_COUNT) exported
from the analytics/chart configuration or the component that defines the list of
charts; update the mapping to iterate based on that CHART_COUNT (e.g.,
Array.from({ length: CHART_COUNT })) so the skeleton count automatically stays
in sync whenever charts are added/removed (ensure you add or import CHART_COUNT
and replace occurrences in analyticsContent.tsx where the hardcoded six is
used).

In `@packages/web/src/ee/features/analytics/types.ts`:
- Around line 9-11: The count fields mcp_requests, api_requests, and
active_users in the analytics schema are plain z.number() and should be
restricted to integer, non-negative values; update their validators to use
.int().nonnegative() on each of those fields (locate the schema definition in
types.ts where mcp_requests, api_requests, and active_users are declared) so the
schema enforces integer non-negative counters.

In `@packages/web/src/features/search/searchApi.ts`:
- Around line 31-40: Extract the duplicated audit logic into a helper (e.g.,
auditCodeSearch) and replace the repeated blocks in search() and streamSearch()
with a single call to that helper; the helper should capture the source via
(await headers()).get('X-Sourcebot-Client-Source') ?? undefined and call
getAuditService().createAudit({ action: 'user.performed_code_search', actor: {
id: user.id, type: 'user' }, target: { id: org.id.toString(), type: 'org' },
orgId: org.id, metadata: { source } }).catch(() => {}), then invoke
auditCodeSearch(user, org) from both search() and streamSearch() to remove
duplication.

ℹ️ Review info

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 8e0e737 and 6726cf2.

📒 Files selected for processing (10)
  • CHANGELOG.md
  • packages/web/src/app/[domain]/components/searchBar/searchBar.tsx
  • packages/web/src/app/api/(server)/repos/listReposApi.ts
  • packages/web/src/ee/features/analytics/actions.ts
  • packages/web/src/ee/features/analytics/analyticsContent.tsx
  • packages/web/src/ee/features/analytics/types.ts
  • packages/web/src/ee/features/audit/types.ts
  • packages/web/src/features/git/getFileSourceApi.ts
  • packages/web/src/features/git/getTreeApi.ts
  • packages/web/src/features/search/searchApi.ts
💤 Files with no reviewable changes (1)
  • packages/web/src/app/[domain]/components/searchBar/searchBar.tsx

## [Unreleased]

### Added
- Added MCP and API key usage tracking to analytics dashboard. Move audit events from client-side to service functions to capture all API calls (web UI, MCP, and non-MCP). Display MCP requests and API requests on separate charts. [#948](https://github.com/sourcebot-dev/sourcebot/pull/948)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Use consistent past tense in the changelog sentence.

Line 11 says “Move audit events…” while the rest of the entry is in past tense. This reads awkwardly in release notes.

✏️ Proposed wording tweak
-- Added MCP and API key usage tracking to analytics dashboard. Move audit events from client-side to service functions to capture all API calls (web UI, MCP, and non-MCP). Display MCP requests and API requests on separate charts. [`#948`](https://github.com/sourcebot-dev/sourcebot/pull/948)
+- Added MCP and API key usage tracking to the analytics dashboard. Moved audit events from client-side to service functions to capture all API calls (web UI, MCP, and non-MCP). Displayed MCP request and API request counts on separate charts. [`#948`](https://github.com/sourcebot-dev/sourcebot/pull/948)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- Added MCP and API key usage tracking to analytics dashboard. Move audit events from client-side to service functions to capture all API calls (web UI, MCP, and non-MCP). Display MCP requests and API requests on separate charts. [#948](https://github.com/sourcebot-dev/sourcebot/pull/948)
- Added MCP and API key usage tracking to the analytics dashboard. Moved audit events from client-side to service functions to capture all API calls (web UI, MCP, and non-MCP). Displayed MCP request and API request counts on separate charts. [`#948`](https://github.com/sourcebot-dev/sourcebot/pull/948)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@CHANGELOG.md` at line 11, The changelog entry mixes tenses—change the phrase
"Move audit events from client-side to service functions to capture all API
calls (web UI, MCP, and non-MCP)" to past tense (e.g., "Moved audit events from
client-side to service functions to capture all API calls (web UI, MCP, and
non-MCP)") so the entire bullet reads consistently in past tense with the rest
of the entry.

- Add SOURCEBOT_EE_AUDIT_RETENTION_DAYS env var (default 180) and AuditLogPruner background job that prunes old audit records daily in batches
- Surface retention period and oldest record date in analytics page header
- Update audit action types table in docs (remove 4 stale, add 11 missing)
- Add audit log storage section to sizing guide with enterprise callout and storage estimates
- Update mock data script with mixed-usage user profiles and new audit actions

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@msukkari msukkari closed this Feb 27, 2026
@msukkari msukkari deleted the msukkari/mcp-analytics branch February 27, 2026 01:00
msukkari added a commit that referenced this pull request Feb 27, 2026
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant