feat(cli): add mcp command — stdio MCP server for AgentMeter spend data - #3
Conversation
There was a problem hiding this comment.
Review summary
The overall design is solid — fetchJson is a clean, defensive helper with proper Zod validation on every API response, error paths return human-readable messages instead of throwing, and stdout is never touched directly (MCP protocol owns it). Good use of encodeURIComponent on sessionId in the URL.
Two issues need to be addressed before merge:
1. readSyncState() not wrapped in try/catch (both tool handlers)
list_recent_sessions (line 162) and get_session (line ~244) call readSyncState() bare. If the sync-state file is absent, locked, or malformed, the throw propagates out of the MCP tool handler. Project convention: "the CLI should never crash with an unhandled exception — catch, log, continue." Wrap in try/catch and return textResult({ error: ... }).
2. list_recent_sessions description says "Works without an API key" — but it doesn't
startMcpServer() exits before registering any tools if getEffectiveConfig() returns nothing. The description is factually incorrect. Either relax the startup check (check only when a tool that needs the API is called) or remove that sentence. Details in the inline comment.
Missing tests
No tests were added for the new command. At minimum, test:
fetchJsonwith mockedfetch(success, network error, non-2xx, bad JSON, Zod mismatch)list_recent_sessionssession-sorting andlimitslicing logic against a fixture sync-stateget_sessionlocal-first / API-fallback branching
The __tests__/ directory mirrors src/ — add __tests__/commands/mcp.test.ts.
Generated by Agent: Code Review for issue #3 · ● 151.2K
…I key - list_recent_sessions and get_session local path require no API key - API-requiring tools (get_my_spend, get_session fallback, get_team_spend) return a structured error + sign-up hint instead of exiting - Wrap readSyncState() in try/catch in all callers - Export fetchJson and handler functions for unit testing
… setup Also adds __tests__/commands/mcp.test.ts (16 tests covering fetchJson, list_recent_sessions, get_session, get_my_spend, get_team_spend)
…t by account tier
- Add tokens field to SyncedSessionSchema (optional, backwards-compat) - Write session.tokens to sync-state during submitAll - Include tokens in list_recent_sessions and get_session local output - Update README: token questions are answerable locally without an account
- Add trimSyncState() — drops completed sessions older than 90 days and caps completed entries at 5,000 (most recent kept); running sessions are always preserved for vanished-session detection - writeSyncState calls trimSyncState before persisting - Trimmed sessions re-submitted on next scan receive 409 (handled gracefully) - 3 new tests covering age cutoff, running-session preservation, count cap
Age cutoff caused Claude Code users to re-submit their full session history on the next sync after a trim, since the scanner reads all JSONL files on every run. A 5,000-entry count cap is sufficient: 2.5 MB max, ~8 months of heavy usage before it activates, and re-submissions are safe (API is idempotent for known session IDs). Also correct the 409 claim in comments — internal docs confirm the API returns 200/201 (update) for duplicate session IDs, not 409.
- list_recent_sessions and get_session now scan Claude/Cursor data live at call time instead of reading sync-state.json - 60-second in-memory cache (per MCP server process) keeps subsequent calls within the TTL window instant - list_recent_sessions filters to last 12 months; get_session searches full scan with no time limit - costCents is enriched from sync-state when available (AgentMeter account) but the tools work fully without it - No CLI setup or background service required for local-only MCP use — adding the MCP config JSON is the only step - 22 tests: scanner mocks, cache hit, 12-month filter, costCents merge, API fallback paths
|
Bugbot is not enabled for your account, so this pull request was not reviewed. Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs. |
Summary
Adds
agentmeter mcp— a stdio MCP server that lets any MCP-compatible AI agent (Claude Code, Cursor, etc.) query session history and spend data without leaving their workflow.Zero setup for local users. Add two lines of JSON to your MCP client config. Done. The server scans Claude Code and Cursor data on demand — no account, no
sync, no background service required.Architecture
Local tools — no account needed
list_recent_sessionsandget_sessioncall the Claude/Cursor scanners directly at tool-call time. Results are cached in memory for 60 seconds per MCP server process (one per client connection), so the first call does the scan and subsequent calls within the window are instant.Sessions from the last 12 months are returned, including full token counts (input/output/cache), duration, model, engine, and repo.
costCentsisnullwithout an account — it gets populated automatically when sessions are submitted to the AgentMeter API and written back to sync-state.API tools — require account
get_my_spendandget_team_spendcall the AgentMeter API directly. If no key is configured they return a structured{ error, hint }with a sign-up link — the server keeps running, tools degrade gracefully.get_session— hybridSearches the live scan first (no API needed). Falls back to
GET /api/runs/<id>only if the session isn't found locally and a key is configured.sync-state.json — role clarified
Stays as an API deduplication cache only. The MCP tools no longer depend on it for local session data.
costCentsenrichment reads from it opportunistically when present.Files changed
src/commands/mcp.tsgetLocalSessions()— runs ClaudeScanner + CursorScanner with in-memory TTL cachehandleListRecentSessions,handleGetSession,handleGetMySpend,handleGetTeamSpend,fetchJsonsrc/schemas/sync-state.tstokensfield — persists token counts when syncing to APIsrc/commands/sync.tstokensto sync-state after each successful API submissionsrc/services/sync-state.tstrimSyncState()— count cap at 5,000 completed entries (no age cutoff)__tests__/commands/mcp.test.ts(22 tests)costCentsenrichment, API fallback pathsREADME.md