Tracklution
Use Tracklution MCP to install and configure server-side conversion tracking with your AI coding agent.
What it can do
- Get Status: Connectivity check only — does NOT return any analytics data. Pings every connected Tracklution server and reports reachable / unreachable. USE WHEN: - The user reports the integration "is
- Get Api Key Info: API key inspection only — does NOT return any analytics data. Shows the key name, prefix, how many containers it can access, and when it expires, for every connected server. USE WHEN
- List Containers: Entry-point tool. Lists every tracking container the user can access across all connected servers. Each container is one tracked website or app. USE WHEN: - Starting any session — you
What data it sees
Do you need an account
No: the server works without sign-in
Use Tracklution MCP to install and configure server-side conversion tracking with your AI coding agent. Verify your setup, troubleshoot issues, and query performance data.
How to connect
How to connect Tracklution to Claude
The server address to paste into Claude settings:
https://server-side-tracking--tracklution.run.tools
- Open Claude (claude.ai or the desktop app).
- Go to Settings → Connectors.
- Click Add custom connector.
- Paste the server address copied below into Remote MCP server URL and click Add.
- In a chat, click + → Connectors and switch the new connector on.
Custom connectors are available on Free, Pro, Max, Team and Enterprise plans (Free is limited to one). On Team and Enterprise an organization Owner adds the connector first under Organization settings → Connectors.
Authorization
After you click Add, a sign-in window for the service opens. Sign in with your own account and approve access. Claude never sees your password.
How to connect Tracklution to ChatGPT
The server address to paste into ChatGPT settings:
https://server-side-tracking--tracklution.run.tools
- Open ChatGPT in a browser (chatgpt.com). A Plus, Pro, Business, Enterprise or Edu plan is required.
- Turn on developer mode once: Settings → Apps → Advanced settings → Developer mode.
- Open Settings → Connectors and click Create.
- Fill in the form: Name (anything), Description (one line about what the service does), MCP server URL (copy it below).
- Under Authentication choose OAuth if the service requires sign-in, otherwise None. Click Create.
- In a new chat open + → Apps/Connectors and enable the connector.
OpenAI has renamed this section before (Connectors → Apps/Plugins). If the label differs, search settings for "developer mode". On Business/Enterprise workspaces an admin must allow custom connectors first.
Authorization
On first use ChatGPT opens the service's sign-in window. Sign in and approve access.
How to connect Tracklution to Cursor
The server address to paste into Cursor settings:
https://server-side-tracking--tracklution.run.tools
Fastest: click Open in Cursor below and confirm the prompt.
Manually:
- In Cursor open Settings → Cursor Settings → MCP and click Add new global MCP server.
- Paste the JSON copied below into
~/.cursor/mcp.json(per project:.cursor/mcp.jsonin the repo root). - Save the file. The server appears in the MCP list; authorize it there if asked.
Authorization
If the service needs sign-in, an authorize button appears next to the server in the MCP list.
Server tool list (18)
Raw names from tools/list. Only developers need these.
| get_status | Connectivity check only — does NOT return any analytics data. Pings every connected Tracklution server and reports reachable / unreachable. USE WHEN: - The user reports the integration "isn't working" and you need to isolate network vs. data issues. - You want to confirm a freshly-added server is online before running real queries. For any data question (counts, events, sessions), skip this and go straight to get_summary or get_report. |
| get_api_key_info | API key inspection only — does NOT return any analytics data. Shows the key name, prefix, how many containers it can access, and when it expires, for every connected server. USE WHEN: - The user asks "which account / key am I connected with?" or "when does my key expire?". - You hit an authorization error and need to confirm the key is still valid. For any data question, skip this and go straight to get_summary or get_report. |
| list_containers | Entry-point tool. Lists every tracking container the user can access across all connected servers. Each container is one tracked website or app. USE WHEN: - Starting any session — you need a container_id before calling get_report, get_summary, list_events, or list_sessions. - The user asks "which sites / accounts can I see?" or names a brand without giving a trc_ ID. - You need the server_name for multi-server setups. - **OAuth fallback path of the agent install contract** (https://www.tracklution.com/agent-install.md, Turn 4 Step 2) — after the user completes browser-OAuth, call this tool to enumerate the user's containers and resolve the (container_id, container_hash) pair you need for onboarding tools (get_installation_scripts, verify_and_score, create_login_link, ...). Match against the user's website URL via the `domain` field (compare by host name — `domain` may be `null` on freshly-created containers, skip those). Returns container IDs (trc_...), container hash (dashboard-URL identifier AND secret half of the dual-key auth-token cache lookup; pass as `container_hash` on every onboarding-tool call — may be `null` on legacy Laravel deploys that pre-date the field, in which case fall back to register_and_provision with auth_token + website_url), titles, domains, currency, timezone, and active status. |
| get_container | Detail view of one container — reveals which event names are tracked and which connectors (Google Ads, Facebook, GA4, etc.) are active. USE WHEN: - You need the exact event names to pass as event_name filter on get_report / list_events (don't guess names like "Purchase" — check here first). - You need the connector alias for get_report's connector= filter. - The user asks "what's set up on site X?" or "which integrations are live?". Call list_containers first to find the trc_ ID. Returns no time-series or event data — pair with get_summary / get_report for that. |
| get_report | Aggregated analytics broken down by dimension and/or time. Fast — hits the pre-aggregated reporting endpoint, not the raw events table. USE WHEN the user asks for counts, values, or trends grouped by anything: - by event name ("how many Purchases vs Leads?") → dimensions=['event_name'] - by traffic source ("top sources?", "Google vs Facebook?") → dimensions=['source'] - by day / week / month ("daily conversions", "weekly trend") → granularity + read daily_series in response - by hour, device, or browser → dimensions=['hour'|'device'|'browser'] - first-click vs last-click attribution → attribution='first_click' - filtered to one delivery integration ("events delivered to Facebook CAPI") → connector='facebook' Prefer this over list_events / list_sessions for ANY question that resolves to a number, a chart, or a ranking — it is orders of magnitude faster. For a single-period overall snapshot (no dimension breakdown), use get_summary instead. Date range required, max 30 days. Call get_container first if you need the list of valid event names for this container. |
| get_summary | Aggregated performance snapshot for one container over a date range. Fast — hits the pre-aggregated reporting endpoint, not the raw events table. USE WHEN the user asks: - "How is my site performing last week?" - "How many events / what's the total value / how many emails collected?" - "What's my recovery rate / how many ad-blocked events?" - "Quick breakdown by traffic source" Returns totals, consent stats, recovery (adblock + restricted-browser) stats, and a per-source breakdown — all in one call. This is the DEFAULT first call for any single-period performance question. Only escalate to get_report when the user wants a breakdown by a specific dimension (event_name, day, hour, device, browser) or a time series. Date range required, max 30 days. |
| list_events | Fetch ONE event by its ID. The only way to use this tool is to pass a known event_id (starts with "evt_" or "TE"). Listing / filtering by date, source, status, or has_* flags is NOT supported on this tool. USE WHEN: - The user references a specific event ID (TE... or evt_...) — pass it as event_id. DON'T USE for counts, totals, top-N rankings, trends, or any breakdown by source / event_name / day — use get_report instead. For an overall performance snapshot, use get_summary. If the user genuinely needs raw events, ask them to look one up in the Tracklution dashboard and provide its ID. Returns a one-element events array (the same shape as before) so existing parsers keep working. |
| list_sessions | Fetch ONE visitor session by its ID, with the full event timeline, UTM params, device info, country, and event counts. The only way to use this tool is to pass a known session_id (starts with "ses_" or "VS"). Listing / filtering by date, source, or has_* flags is NOT supported on this tool. USE WHEN: - The user references a specific session ID (VS... or ses_...) — pass it as session_id. DON'T USE for session counts, conversion rates, top sources, or any "how many sessions" / "show me sessions" question — use get_report or get_summary instead (orders of magnitude faster). If the user genuinely needs to inspect a session, ask them to look one up in the Tracklution dashboard and provide its ID. Returns a one-element sessions array (the same shape as before) so existing parsers keep working. |
| query_events | Note: this tool holds data for a limited timeframe — only the last ~7-14 days are available, depending on the container. Paginated list of raw events for a date range, with filters (event_name, source, status, and 10 has_* click-ID presence flags). DATA RETENTION: the v2 store currently holds only the last ~7-14 days. Older date_from values pass validation but return empty rows. A "warnings" array is added when date_from is outside the retention window. USE WHEN: - User wants to loop through all events for a given date range, inspecting data from each one in detail. - User wants to enumerate events matching a filter (e.g. "show all Purchase events with gclid this week", "list error events from yesterday"). - User explicitly wants raw event records, not aggregates. DON'T USE for counts, totals, top-N rankings, breakdowns by source/day — use get_report (orders of magnitude faster on aggregates). For a single event by ID, use list_events. STATUS FILTER: defaults to "processed" — these are the real, successfully-recorded events that nearly every analytics question is about. Only set status explicitly when debugging delivery/ingestion issues: "pending" (queued, not yet processed), "error" (processing failed), "rejected" (validation/spam blocked). Asking for non-processed events without a debugging reason will mislead the user. Pagination is cursor-based: pass the returned meta.cursor.next_cursor back as cursor on the next call. Stop when has_more is false. Backend caps date range at 30 days. |
| query_sessions | Note: this tool holds data for a limited timeframe — only the last ~7-14 days are available, depending on the container. Paginated list of visitor sessions for a date range, with filters (source and 10 has_* click-ID presence flags). Each session record includes UTM params, device info, country, event counts, contact-presence flags (no raw PII), and the full event timeline. DATA RETENTION: the v2 store currently holds only the last ~7-14 days. Older date_from values pass validation but return empty rows. A "warnings" array is added when date_from is outside the retention window. USE WHEN: - User wants to loop through all sessions for a given date range, inspecting data from each one in detail. - User wants to enumerate sessions matching a filter (e.g. "list sessions from organic with adblock this week"). - User explicitly wants raw session records, not aggregates. DON'T USE for session counts, top sources, or "how many sessions" / conversion-rate questions — use get_report or get_summary (orders of magnitude faster). For a single session by ID, use list_sessions. Pagination is cursor-based: pass the returned meta.cursor.next_cursor back as cursor on the next call. Stop when has_more is false. Backend caps date range at 30 days. |
| scout_website | Public discovery; safe to call without auth. Detects CMS/platform hints and creates an onboarding session. Returns an `onboarding_session_id` and a single-use `_sensitive_onboarding_session_token` used by `register_and_provision` to link this scout to the provision. Every response includes `_request_id` in `structuredContent`. When reporting issues to Tracklution support, include this value verbatim — it correlates the call across both the MCP server and the Laravel backend logs. |
| register_and_provision | Create/reuse an account and the first tracking container in one agent-safe call. For brand-new emails: the API creates the account, immediately emails the user a welcome / set-password link, and returns a 60-minute JWT in `data.auth._sensitive_access_token` so this agent can continue applying snippets and verifying events without further authentication. Hold the JWT only for this conversation and pass it back as `auth_token` on subsequent onboarding tool calls. After the user clicks the welcome email link, they finish setup and log into the dashboard. The response carries two informational flags for fresh registrations: `pending_user_activation: true` and `welcome_email_sent_to: <email>`. Surface the inbox-check instruction to the user verbatim when these are present. PROACTIVE auth_token: If you ALREADY have an `auth_token` (the user is signed in, or a previous turn returned one), pass it INSTEAD of `email`. With `auth_token` the call returns the existing-or-new container for that (user, website_url) pair — no `duplicate_account` error occurs. Do NOT pass `name` or `email` alongside `auth_token`; the server reads identity from the JWT. RECOVERY on `duplicate_account` (HTTP 409): the user already has a Tracklution account. Ask them to log in at https://app.tracklution.com (or request a fresh setup link at https://app.tracklution.com/account-recovery if they never finished the welcome flow), then re-run this tool with `auth_token` in memory. RECOVERY on `multi_client_ambiguous` (HTTP 409): the JWT-authed user has multiple companies (clients) and the requested `website_url` does not match any existing container. The server cannot pick a company on the user's behalf. Surface `next_action.reason` verbatim (it contains a dashboard URL) and EXIT — do NOT call any other MCP tool. The user finishes the container creation from their dashboard, then can re-run with the resulting `container_id`. RECOVERY on `rate_limited` (HTTP 429): respect `retry_after_seconds` strictly. Never retry without backoff. If `errors[0].details.key` mentions `domain:<host>`, the cap is per-website-domain — a different `email` will NOT bypass it. The MCP server does NOT generate idempotency keys — the agent MUST supply `idempotency_key` (UUID v7 recommended). Every response includes `_request_id` in `structuredContent`. |
| get_installation_scripts | Return paste-safe tracking snippets, webhook templates, recommended events, common mistakes, and verification instructions for the given framework. Sensitive: the response includes a `_sensitive_webhook` block — only `structuredContent` carries the live URL. Every response includes `_request_id` in `structuredContent`. APPLY EVERY RELEVANT SNIPPET: the response's `framework_snippets` keys are typically `init`, `page_view`, `purchase`, `lead`, `contact_info`. Apply ALL that match the project's surface: - `init` — always required (loads the tracker). - `page_view` — always required (the first signal of installation). - `purchase` — when the project has a checkout/order success flow (`has_checkout=true`). - `lead` — when the project is a lead-gen funnel. - `contact_info` — when the project has an authenticated user view (`has_login=true`). SKIPPING `contact_info` causes `verify_and_score.scoring_complete` to stay `false` indefinitely; the user will see 'almost done' forever. READ `common_mistakes[]` PER SNIPPET: the Tracklution tracking engine is intentionally robust (deduplicates repeat Purchases, accepts late ContactInfo, absorbs React Strict Mode double-fires), so the `common_mistakes` are framed as OPTIMAL-vs-acceptable, not breakage warnings. Follow them when you can; their absence does NOT break tracking, but their presence makes the developer's network log cleaner. |
| select_installation_method | Persist the chosen installation method on the container. Mutating — requires an agent-supplied `idempotency_key`. Every response includes `_request_id` in `structuredContent`. |
| verify_and_score | Force-process recent events, recalculate scoring, and return a strict verification diagnosis. Mutating — requires an agent-supplied `idempotency_key`. Every response includes `_request_id` in `structuredContent`. COMPLETION CONTRACT (read this every time): The success signal is the envelope's top-level `status`, NOT `verification.scoring_complete`. A fresh install can sit at `scoring_complete=false` indefinitely because of organic metrics (e.g. recovery-rate-based scoring) that mature only as real traffic accumulates — using `scoring_complete` as the completion gate would mean no install ever appears done. Use `status` + `verification.not_ready_reason` (equivalently: `not_ready_reason === null`) as the gate. READING `status` + `verification.not_ready_reason` — three behavioral classes: (A) DONE / hand-off-to-user (no retry, transition to Turn 5 scored hand-off): - `status='ok'` (`not_ready_reason === null`) — install complete on both sides. Call `get_next_steps` to grab `overall_progress` + `next_steps[]`, then `create_login_link` (target_page=dashboard), then send the Turn 5 scored hand-off (see agent-install.md Step 5). - `not_ready_reason='awaiting_connector_activation'` — your code is done; user activates connector in dashboard. Call `create_login_link` (target_page=connectors), send the scored hand-off naming the connector step, and EXIT. `retry.max_retries_recommended === 0`. - `not_ready_reason='awaiting_first_party_mode'` — same shape, target_page=dns. EXIT. - `not_ready_reason='only_pageview_seen'` — code is in, loader works, only PageView received. This is the EXPECTED day-0 state for a fresh install: the user hasn't performed a Purchase/Lead yet, and a 30-second wait will NOT make them click 'Buy' on their own site. Do NOT retry. Transition to the Turn 5 scored hand-off, surface the score, and instruct the user to trigger a real event on the live site, then reply `verify` for a re-check. - `not_ready_reason='missing_bottom_funnel_event'` — same semantics as `only_pageview_seen` (no bottom-funnel event yet). Do NOT retry. Transition to scored hand-off; user must trigger the missing event on the live site. (B) TRANSIENT / retry with budget (3 × 30s, then graceful exit): - `not_ready_reason='no_events_after_install'` — loader hasn't reported yet (post-deploy delay or local-dev environment). Retry per `retry.retry_after_seconds` / `retry.max_retries_recommended`. On budget exhaustion, transition to the Turn 5 scored hand-off with `events haven't arrived yet — trigger a PageView on the site and reply 'verify'` instead of declaring failure. - `not_ready_reason='script_not_seen'` — bootstrap script not loaded. Same retry shape as above. - `not_ready_reason='event_not_received_yet'` — a specific expected_event hasn't arrived. Same retry shape. - `not_ready_reason='events_processing'` — initial-calculate race. Same retry shape; on exhaustion say `events are flowing; final scoring will catch up within a few minutes` and exit. (C) CODE-FIX / retry ONCE after fix (do not loop): - `not_ready_reason='missing_contact_info'` — `tlq('set','ContactInfo',...)` not seen. Apply the missing snippet (see `get_installation_scripts.framework_snippets.contact_info`), then call this tool ONCE more. If still missing, transition to scored hand-off; the next_steps list will name `implement_contact_info` as a remaining task. - `not_ready_reason='domain_mismatch'` — events arrived from the wrong host. Inspect deploy target, fix, retry ONCE. On failure, transition to scored hand-off. DATA FIELDS YOU MUST READ: - `data.event_names_seen[]` — distinct `track`-type events received (PageView, Purchase, Lead, ...). - `data.set_events_seen[]` — distinct `set`-type metadata received (ContactInfo, ...). Do NOT expect ContactInfo to appear in `event_names_seen`; it lives ONLY in `set_events_seen` because `tlq('set', 'ContactInfo', ...)` is metadata, not an event. - `data.verification.not_ready_reason` — stable enum, full list: `no_events_after_install`, `event_not_received_yet`, `script_not_seen`, `only_pageview_seen`, `missing_bottom_funnel_event`, `missing_contact_info`, `domain_mismatch`, `awaiting_connector_activation`, `awaiting_first_party_mode`, `events_processing`. Map verbatim to user-friendly copy via `verification.message`. IDEMPOTENCY: use the SAME `idempotency_key` to dedupe within a 30s window (returns the cached response). Use a FRESH `idempotency_key` after the window to trigger a new force-process pass over recent events. |
| get_next_steps | Return scoring-driven ordered guidance for the agent. Non-mutating. Pass `recalculate=true` only when you explicitly want to force a fresh scoring pass (expensive). Every response includes `_request_id` in `structuredContent`. |
| get_onboarding_session | Replayable onboarding transcript. Use the JWT when the session has a user; use the pre-auth `_sensitive_onboarding_session_token` from scout when the session is pre-auth. Every response includes `_request_id` in `structuredContent`. |
| create_login_link | Create a one-time dashboard handoff URL after AI setup. The login URL contains a single-use token. Do not write it to disk or echo it back. Pass it directly to the user. Returns `login_link_consume_disabled` (HTTP 503) when the feature flag is off — in that case, ask the user to log in manually. Every response includes `_request_id` in `structuredContent`. DO NOT use this tool to answer analytics or stats questions. It is a single-use dashboard handoff URL for post-install navigation only. For data questions, call the analytics tools directly (`get_summary` / `get_report` / `list_containers` / etc.); the MCP host will automatically open an OAuth dialog inline if the user has not authenticated yet, and will retry the call once authentication completes. |