Skip to content
Last9
Book demo

Cursor

Send Cursor IDE session telemetry — prompts, tool calls, subagent spans, and token usage — to Last9 via OpenTelemetry.

Cursor generates a lot of signal. Sessions, prompts, tool calls, model switches, compactions, subagents. None of it goes anywhere by default. cursorscope — an open-source collector built by Last9 — fixes that.

cursorscope is a small Node.js service that intercepts Cursor’s hook events and exports them as OpenTelemetry traces, metrics, and logs to Last9. You get full observability into how your team uses Cursor: which models they reach for, how long agent loops run, where tools fail, how token budgets are spent.

It exports three OpenTelemetry signal types:

  • Traces — one root span per session, prompt spans as children, tool call spans underneath those, subagent spans branching off when Cursor fans out work
  • Metrics — counters and histograms for sessions, prompts, tool executions, lines of code, MCP invocations, and token usage
  • Logs — one structured log record per hook event with conversation ID, model, and hook-specific fields

What gets exported

Traces

cursorscope models each Cursor session as a distributed trace. Spans are correlated by cursor.conversation.id and cursor.generation.id so tool calls nest under their parent prompt.

SpanEmitted forKey attributes
invoke_agent CursorSession start, each promptcursor.user, cursor.user.email, cursor.repo, gen_ai.request.model, gen_ai.conversation.id
execute_tool <name>Each tool invocationgen_ai.tool.name, gen_ai.tool.type, gen_ai.tool.call.id, cursor.attribution.category
invoke_agent <subagent>Subagent start/stopcursor.is_background_agent, cursor.subagent.model

Span events on session spans:

EventEmitted when
gen_ai.client.session.endSession ends (includes duration, final status)
gen_ai.client.inference.operation.detailsAfter agent response (includes token counts)

Metrics

MetricUnitKey attributes
cursor_hook_events_totalcountcursor_user, cursor_model, cursor_repo
cursor_session_totalcountcursor_user, cursor_model, cursor_repo
cursor_prompt_totalcountcursor_user, cursor_model, cursor_repo
cursor_tool_executions_totalcounttool_name, success, cursor_user
cursor_lines_of_code_totalcounttype (added/removed), cursor_user
cursor_mcp_invocations_totalcountcursor.mcp.server, cursor.mcp.tool
cursor_attribution_invocations_totalcountcursor.attribution.category, cursor.attribution.name
cursor_attributed_context_tokens_totalcountcursor.attribution.category, cursor.attribution.name
gen_ai.client.operation.durationseconds (histogram)gen_ai.operation.name, gen_ai.request.model
gen_ai.client.token.usagetokens (histogram)gen_ai.token.type, gen_ai.request.model
cursor_api_metric_valuegaugemetric_name (Admin API polling)

Logs

One structured log record per hook event. All records share cursor.conversation.id so you can reconstruct a full session sequence in Last9 Logs.

Key log fields: hook_event_name, cursor.user, cursor.user.email, cursor.repo, gen_ai.request.model, cursor.conversation.id, cursor.generation.id.

Prerequisites

  1. Last9 account — Sign up at app.last9.io
  2. Cursor IDE — Any version with hook support
  3. Node.js 20+ — Required to run the cursorscope ingestor
  4. OTLP credentials — Get your endpoint and auth header from Integrations → OpenTelemetry

Setup

  1. Get your Last9 OTLP credentials

    Navigate to Integrations → OpenTelemetry in your Last9 dashboard. Copy:

    • OTLP Endpoint (e.g., https://otlp-aps1.last9.io:443)
    • Authorization header (e.g., Basic <base64-token>)
  2. Run guided setup

    npx @last9/cursorscope

    This opens the Last9 OpenTelemetry integration page (or prints the link), then prompts for your endpoint and auth token.

    Setup installs cursorscope to ~/.cursorscope, writes .env with your credentials, registers global Cursor hooks in ~/.cursor/hooks.json, and starts the ingestor.

  3. Restart Cursor and send one Agent message

    The ingestor starts in the background automatically on the next Cursor hook event. Logs at ~/.cursor/cursorscope.log.

  4. Verify data is arriving

    • Traces — open Traces in Last9, filter by service.name = cursorscope
    • Metrics — open Metrics, search for cursor_prompt_total or gen_ai_client_operation_duration
    • Logs — open Logs, filter by service.name = cursorscope

    Check ingestor health:

    curl http://127.0.0.1:4327/healthz
    # {"ok":true,"service":"cursorscope","version":"..."}

Configuration reference

cursorscope is configured via ~/.cursorscope/.env.

OTLP export

VariableDefaultDescription
OTEL_EXPORTER_OTLP_TRACES_ENDPOINTLast9 traces endpoint (/v1/traces)
OTEL_EXPORTER_OTLP_METRICS_ENDPOINTLast9 metrics endpoint (/v1/metrics)
OTEL_EXPORTER_OTLP_LOGS_ENDPOINTLast9 logs endpoint (/v1/logs)
OTEL_EXPORTER_OTLP_HEADERSAuthorization=Basic <token>
OTEL_EXPORTER_OTLP_METRICS_TEMPORALITY_PREFERENCEcumulativeRequired by Last9 — leave as cumulative
OTEL_SERVICE_NAMEcursorscopeService name tag on all signals

Privacy

VariableDefaultDescription
CURSOR_LOG_USER_PROMPTSfalseInclude full prompt text in logs
CURSOR_LOG_TOOL_DETAILSfalseInclude tool input/output in spans
CURSOR_MASK_USER_EMAILfalseMask cursor.user.email to u*****r@domain.com before export

Ingestor

VariableDefaultDescription
PORT4327Local HTTP port for the ingestor
CURSORSCOPE_AUTO_STARTtrueAuto-start ingestor when a hook fires
METRIC_EXPORT_INTERVAL_MS15000Metric flush interval in milliseconds

Cursor Admin API polling

For Cursor Business accounts — exports team-level daily usage as OTel gauges.

VariableDefaultDescription
ENABLE_CURSOR_API_POLLINGfalseEnable Admin API polling
CURSOR_ADMIN_API_KEYCursor Admin API key
CURSOR_TEAM_IDCursor team ID
CURSOR_API_POLL_INTERVAL_MS300000Poll interval in milliseconds

What you can do in Last9

Session traces with nested tool calls

Every agent interaction produces a distributed trace rooted at invoke_agent Cursor. Tool calls appear as child spans with duration and success/failure status. Subagent spans branch off when Cursor fans out work in parallel.

Filter Last9 Traces by service.name = cursorscope and drill into any span to see the full conversation context: model, repo, user, conversation ID.

Token usage and model efficiency (metrics)

gen_ai.client.token.usage is a histogram broken down by token type and model. Compare token counts across models to track efficiency as your team experiments with model selection.

Tool call latency and failure rates (metrics + traces)

cursor_tool_executions_total segmented by tool_name and success shows which tools fail most often. Cross-reference with gen_ai.client.operation.duration to find which tools dominate session time.

MCP server attribution (metrics)

cursor_mcp_invocations_total and cursor_attributed_context_tokens_total reveal which MCP servers are responsible for context token consumption — useful for spotting noisy servers or inefficient tool patterns.

Lines of code output (metrics)

cursor_lines_of_code_total tracks lines added and removed by Cursor agent and Tab edits, broken down by user. Useful for adoption reporting and measuring AI-assisted output.

Session replay via cursor.conversation.id (logs)

Every log record carries cursor.conversation.id. Filter Last9 Logs by a conversation ID to reconstruct the full event sequence for one session:

sessionStart → beforeSubmitPrompt → preToolUse → postToolUse → afterAgentResponse → sessionEnd

Per-user usage breakdowns

All metrics carry cursor_user (the Cursor user email). Group by cursor_user to see per-developer prompt counts, tool usage, and token spend — useful for team leads tracking AI adoption or budget attribution.


Troubleshooting

  • No data in Last9 after sending an Agent message

    • Confirm the ingestor is running: curl http://127.0.0.1:4327/healthz
    • Check logs for export errors: tail -50 ~/.cursor/cursorscope.log — look for [otel-export] export failed
    • Probe OTLP connectivity: curl http://127.0.0.1:4327/debug/otlp-probe
    • Verify hooks are registered: ~/.cursor/hooks.json should contain entries calling cursorscope-forward.sh
  • Export failing with ENOTFOUND or ETIMEDOUT

    DNS resolution failures happen after VPN toggles or laptop sleep/wake. The ingestor’s DNS resolver doesn’t retry cached failures. Fix: restart the ingestor.

    npx @last9/cursorscope stop
    npx @last9/cursorscope start
  • Ingestor restarts on every hook event

    Multiple concurrent hook calls can trigger version-check races in ensure-cursorscope.sh. This is normal during a version upgrade. If it persists, check ~/.cursor/cursorscope.log for restarting (running vX, expected vY) entries and re-run npx @last9/cursorscope setup.

  • cursor.user appears as unknown in spans

    cursorscope reads user identity from the user_email field in Cursor’s hook payload. This field is populated when Cursor is signed in. If you see unknown, confirm you are logged into Cursor.

  • Metrics appear but traces are missing

    Traces require at least one full session event cycle (sessionStartsessionEnd or afterAgentResponse). Verify you sent an Agent message (not just opened Cursor). Check ~/.cursor/cursorscope.log for span-related errors.

  • 401 / authentication errors

    • Verify header format: Authorization=Basic <token> (no Bearer prefix, no extra quotes)
    • Regenerate the token from Integrations → OpenTelemetry if it has expired
  • Metric names in Last9

    Last9 converts OTel metric names to Prometheus format. Examples:

    cursorscope metricLast9 metric name
    cursor_prompt_totalcursor_prompt_total
    cursor_tool_executions_totalcursor_tool_executions_total
    gen_ai.client.operation.durationgen_ai_client_operation_duration_seconds
    gen_ai.client.token.usagegen_ai_client_token_usage_tokens

Please get in touch with us on Discord or Email if you have any questions.