GTM template with instructions video from Simo Ahava!
Agents fetching this page programmatically can grab the raw markdown at /agent-install.md.
Read once, follow exactly, stay silent between steps. Do not paraphrase this document to the user.
You are an AI coding agent. The user asked you to install Tracklution. This
document is a contract: a fixed conversation. On the magic-install happy
path the agent sends 3 user-facing messages — Turn 1 question, Turn 3
hand-off, Turn 5 scored hand-off. Advanced (OAuth) mode adds the Turn 4
Step 2 OAuth-branch question (4 total). Each user improve reply adds one
Turn 6 propose-diff prompt + a refreshed Turn 5 scored hand-off (cumulative
+2 per loop). The only autonomous install path is the Tracklution MCP
server at https://mcp.tracklution.com/mcp; the REST bootstrap at
https://api.trlution.com/install/quick-setup is the front door to it.
Do not write your own tracking code, do not call ad-platform APIs
directly, do not invent endpoint URLs, do not scrape the dashboard.
For per-host quirks, error code reference, the two-axis framework ×
event_source_system model, and file-safety notes, see
agent-install-reference.md.
Only fetch the reference doc when the contract sends you there explicitly.
create_login_link URL in the
Turn 5 scored hand-off message IS allowed and required.duplicate_account, validation_failed,
multi_client_ambiguous, etc.) to the user. Follow the contract's recovery
branch silently — surface the next_action.reason text instead when the
server provides one.create_login_link, inline the URL into the Turn 5 scored hand-off
message. Do not write it to a file. Do not echo it in a separate "for your
reference" message. The URL is single-use.Before saying anything, do three things silently.
Detect the host by self-knowledge first ("I am Cursor's chat agent" / "I am Claude Code" / etc.). If unclear, look for environmental markers:
| Host | Marker |
|---|---|
| Cursor | .cursor/ directory exists, OR ~/.cursor/mcp.json exists, OR parent process cursor |
| Claude Code | claude on $PATH, OR ~/.claude/ exists |
| Codex CLI | codex on $PATH, OR ~/.codex/ exists |
| Windsurf | ~/.codeium/windsurf/ exists, OR parent process windsurf / Codeium |
| Cline | saoudrizwan.claude-dev globalStorage dir (VS Code) OR ~/.cline/ |
| Lovable | running inside lovable.dev |
| Replit | running inside replit.com |
| Bolt | running inside bolt.new |
If detection is genuinely ambiguous, ask once: "Which agent host are you using — Cursor, Claude Code, Codex, Windsurf, Cline, Lovable, Replit, or Bolt?"
Detect the framework: next.config.{js,mjs,ts} present OR next listed in
package.json dependencies → nextjs. Else → html. Send exactly one of these
two values to the API. (The API also accepts other, but you should never need
it — html is the correct fallback. Even on Shopify / WooCommerce / Stripe
sites, send framework: html — the storefront platform is captured by the
optional event_source_system argument later, NOT by framework.)
Classify the host's install capability:
claude mcp add .... Magic install
works.Use this exact text:
Tracklution needs your email and website URL. Reply with both (e.g.,
you@example.com mysite.com). Type advanced to use OAuth instead.
Then stop and wait.
Skip Turn 1 if the user's initial prompt already contains both an email and a website URL (e.g., "Install Tracklution for alice@x.com on alice.com"). Extract them and proceed silently to magic install.
If exactly one is present (e.g., they gave an email but no URL): ask once, in one sentence: "What's your production website URL?" (or the equivalent for the missing field).
Reject localhost, IP literals, or any host without a public TLD. Ask
once: "That URL looks off — share the production URL like
https://mysite.com."
| User reply pattern | Action |
|---|---|
| Email + URL | Magic install (POST → merge → Turn 3 magic hand-off). |
advanced, oauth, login, sign in, connect, i have an account (case-insensitive, intent match — tolerate typos) | OAuth install (write tokenless config → Turn 3 OAuth hand-off). |
API returns HTTP 409 duplicate_account after magic POST (or next_action.tool === "oauth_fallback") | OAuth fallback (write tokenless config → Turn 3 duplicate hand-off). Do NOT explain the error code. |
| Malformed URL | Ask once (see Turn 1 reject clause), then re-branch. |
Pick the single hand-off line that matches your branch and host class. Send only that line. No preamble, no postscript.
I added the Tracklution MCP. Enable it in your editor's MCP settings, then
reply go.
I added the Tracklution MCP via the Claude CLI. Run /mcp to confirm it's
connected, then reply go.
advanced (file-edit host):
I added a tokenless Tracklution MCP entry. Enable it in your editor and
click Connect to sign in, then reply go.
Looks like you already have a Tracklution account. Enable the Tracklution
MCP and click Connect to sign in, then reply go.
instruction
string from
https://www.tracklution.com/api/install-recipes/
(under mcp_install_methods.<host>.instruction) verbatim. Each is two
sentences telling the user where to add https://mcp.tracklution.com/mcp
in their settings UI, and already ends with then reply `go` .go (silent)Run this sequence silently. Once verify_and_score returns its routing
verdict in Step 5, transition to Turn 5 (scored hand-off) — that's
where the user-facing message goes. The Step 2 OAuth-branch question is
the ONLY allowed agent message during Turn 4.
Call get_status (MCP). Success predicate:
structuredContent.servers[].reachable === true.
If get_status fails, recover per the failure mode:
WWW-Authenticate → silent OAuth fallback. Rewrite the host's
MCP config with the tokenless body (no headers.Authorization). Send the
OAuth duplicate-variant Turn 3 hand-off and wait for go again.The MCP isn't visible in this chat session yet — open a new chat and
say continue Tracklution install, run Cmd/Ctrl+Shift+P → Developer:
Reload Window, or toggle tracklution off/on in your MCP settings.
Then reply go.
container_id + container_hashTwo branches; pick one based on how you got here:
data.container.{id, hash} from
the quick-setup response. Skip to Step 3.advanced, or magic returned oauth_fallback):
call list_containers first to discover what the user already has
access to. Then branch on the user's intent.If list_containers returns ≥1 container, ASK once:
You're signed in. Add Tracklution to a NEW website, or connect to an EXISTING container? Reply new <url> or existing.
Skip Step 2.1 entirely when list_containers returns 0 containers — go
straight to the new <url> branch (it's the only sensible path).
new <url> (synonyms: new, add, install, create):
validate the URL (same rules as Turn 1, reject localhost / IP literals),
then call
register_and_provision({auth_token, website_url, framework, agent_client, idempotency_key}).
DO NOT pass name or email — the server reads identity from the JWT.
The response carries data.container.{id, hash} (and auth will be
null; the existing JWT is still valid).existing (synonyms: existing, connect, pick, choose):
match the user's website URL against list_containers[].domain.
Normalize both sides: lowercase, strip protocol / port / path / query /
trailing slash, strip leading www.. domain may be null on
freshly-created containers — skip those. Use the matched entry's id
and hash for all subsequent onboarding calls. If no match, fall through
to the new <url> branch.multi_client_ambiguousIf register_and_provision returns HTTP 409 with
errors[0].code === 'multi_client_ambiguous': the user's account holds
multiple companies (clients) and the server cannot guess which one the new
container belongs to. The response carries next_action.reason with a
dashboard URL. Send that line verbatim and EXIT. Do NOT call any other MCP
tool — the user finishes the setup in the dashboard.
Call get_installation_scripts({ container_id, container_hash, framework }).
Every onboarding-tool call MUST include container_hash.
Apply every snippet that matches the project surface (init, page_view
always; purchase when the project has checkout; lead for lead-gen;
contact_info when there's an authenticated user view). Silent — no
per-snippet commentary.
not_ready_reason classCall verify_and_score({ container_id, container_hash, idempotency_key }).
Branch on status + verification.not_ready_reason. Three behavioral
classes — pick the right action for the reason returned:
(A) DONE / hand-off-to-user — transition to Turn 5 scored hand-off, no retry:
status: ok (not_ready_reason: null) → install complete on both sides
(code is in AND connectors/FPM done). Go to Turn 5.not_ready_reason: awaiting_connector_activation → your code work is
done; user must activate the connector in the dashboard. Go to Turn 5
(with target_page=connectors for the login link).not_ready_reason: awaiting_first_party_mode → same shape, user must
add a DNS record. Go to Turn 5 (target_page=dns).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 on the live
site, and a 30-second wait will NOT make them do so. Do NOT retry.
Go to Turn 5 — the scored hand-off will tell them which event(s) to
trigger.not_ready_reason: missing_bottom_funnel_event → same semantics as
only_pageview_seen. Do NOT retry. Go to Turn 5.(B) TRANSIENT — retry per retry.retry_after_seconds × retry.max_retries_recommended, then go to Turn 5:
not_ready_reason: no_events_after_install → loader hasn't reported yet
(post-deploy delay or local-dev). Retry per budget (typically 3 × 30s).not_ready_reason: script_not_seen → bootstrap script not loaded. Same.not_ready_reason: event_not_received_yet → specific expected_event not
arrived yet. Same.not_ready_reason: events_processing → initial-calculate race. Same.When the retry budget is exhausted, fall through to Turn 5 (do NOT block — the
install is good enough to score; events will catch up). The Turn 5 message
will instruct the user to trigger a PageView and reply verify.
(C) CODE-FIX — retry exactly ONCE after the agent fixes its code, then go to Turn 5:
not_ready_reason: missing_contact_info → apply the
framework_snippets.contact_info snippet to a signup/login/profile/checkout
form, then call this tool ONCE more with a fresh idempotency_key. If
still missing, fall through to Turn 5.not_ready_reason: domain_mismatch → events arrived from the wrong host;
inspect deploy target, fix, retry ONCE. On failure, fall through to Turn 5.Do not declare "installation complete" until you have surfaced the Turn 5
scored hand-off. The scoring_complete flag is NOT the gate — it may stay
false indefinitely for organic-metric reasons.
After Turn 4 Step 5 routes here, run these three calls silently:
get_next_steps({ container_id, container_hash, recalculate: true }) —
returns data.overall_progress (0-100 int) + data.next_steps[] (each
carries title, message, score_impact, tool_to_call, action).create_login_link({ container_id, container_hash, target_page }) with:
target_page=connectors if Step 5 routed here on
awaiting_connector_activation,target_page=dns if routed on awaiting_first_party_mode,target_page=dashboard otherwise.data.next_steps[] (in returned order; do not
re-sort). For each, you'll surface {title} — {score_impact} verbatim.Then send ONE message using this template (the { } placeholders get
substituted; the verbatim sentences around them DO NOT change):
Tracking is live. Tracklution score: {overall_progress}/100. Top {N} ways to reach 100% — each percent point = more accurate attribution = more efficient ad spend:
Open your dashboard: {_sensitive_login_url}
Reply improve and I'll walk you through the in-code changes one by one, or done to stop here.
Substitution rules:
{N} is min(3, next_steps.length). If next_steps is empty, omit the
bullet list entirely and replace the lead sentence with
Tracklution score: 100/100. Maximum marketing budget efficiency unlocked.{score_impact} is forwarded verbatim from the API (it's already a
formatted string like "+25% on this step"); do NOT parse or rewrite.ok reason, replace the lead
sentence Tracking is live. with the one-sentence verification.message
from verify_and_score (e.g. "Activate your Google Ads connector to start measuring."). Keep the rest of the template.Events haven't arrived yet — trigger a PageView on the site and reply 'verify' once it's live. and still include the score line + top 3.After sending, WAIT for the user's reply. Do NOT call any other MCP tool proactively.
improve)Trigger synonyms (case-insensitive intent match, tolerate typos):
improve, 100, 100%, more, next, score, optimize.
Algorithm (loop until user says done or stops replying):
get_next_steps({ container_id, container_hash, recalculate: false }).next_steps[] whose tool_to_call is non-null
AND action is implement_events or implement_contact_info. Skip
entries with action in {configure_connector, configure_dns, review_step} — those are dashboard actions already linked in Turn 5;
the user handles them in the browser, not via the agent.implement_contact_info → forms with email/phone inputs in
signup/login/profile/checkout views. Common patterns:
**/{sign,log,register}*.{tsx,jsx,vue,php,html}, **/checkout*,
**/contact*, **/auth/*, **/profile/*.implement_events (Lead variant) → contact/quote/demo forms.implement_events (Purchase variant) → checkout success pages,
Stripe webhooks, Shopify/Woo order-complete templates, Next.js API
routes named *checkout*, *order*, *payment*.framework_snippets.* from get_installation_scripts), prepare a ONE-FILE,
ONE-SNIPPET diff. Do NOT invent tracking code; only re-use snippets the
API has handed you. If no matching snippet exists for this step, ask the
user once for guidance and exit.Found <path>:<line>. I'd add this snippet for {step.score_impact}:
<snippet>
Apply? Reply yes / skip / show another.
yes → apply ONLY that one edit. Then call verify_and_score with a
fresh idempotency_key. Take the response back through the Turn 5
scored hand-off (the score should now be higher). Re-enter Turn 6
automatically if user replies improve again.skip → drop that entry; jump back to step 2 and pick the next eligible
step.show another → scan a different surface for the SAME step (step 3
again with a different file pattern); re-send the propose-diff prompt.done or silence → exit the loop.When the user comes back after activating a connector or adding DNS in the
dashboard (e.g. they reply go, done, or verify), re-run
verify_and_score and route through Turn 5 again. The score will reflect the
new connector. When overall_progress >= 100, the Turn 5 template replaces
the bullet list with the maximum-efficiency line above.
U: Install Tracklution.
A: Tracklution needs your email and website URL. Reply with both (e.g.,
you@example.com mysite.com). Type `advanced` to use OAuth instead.
U: alice@shop.com myshop.com
A: [silent: detect host=cursor, framework=nextjs, POST /install/quick-setup,
merge .cursor/mcp.json]
A: I added the Tracklution MCP. Enable it in your editor's MCP settings, then
reply `go`.
U: go
A: [silent: get_status, get_installation_scripts, apply 3 snippets,
verify_and_score -> only_pageview_seen (NO retry, day-0 expected),
get_next_steps, create_login_link target=dashboard]
A: Tracking is live. Tracklution score: 35/100. Top 3 ways to reach 100% — each
percent point = more accurate attribution = more efficient ad spend:
1. Activate your Google Ads connector — +25% on this step
2. Enable First-Party Mode (one DNS record) — +15% on this step
3. Wire ContactInfo on your signup form — +10% on this step
Open your dashboard: https://app.tracklution.com/login/abc123
Reply `improve` and I'll walk you through the in-code changes one by one,
or `done` to stop here.
U: improve
A: [silent: get_next_steps, pick implement_contact_info, scan project ->
found src/app/(auth)/signup/page.tsx]
A: Found `src/app/(auth)/signup/page.tsx:42`. I'd add this snippet for
+10% on this step:
```tsx
tlq('set', 'ContactInfo', { email, phone });
Apply? Reply yes / skip / show another.
U: yes
A: [silent: apply edit, verify_and_score (fresh idempotency_key),
get_next_steps, create_login_link]
A: Tracking is live. Tracklution score: 45/100. Top 2 ways to reach 100%:
improve to keep going, or done to stop here.
U: done
### Duplicate-account silent fallback
U: Install Tracklution.
A: Tracklution needs your email and website URL. Reply with both (e.g.,
you@example.com mysite.com). Type advanced to use OAuth instead.
U: bob@existing.com bobshop.com
A: [silent: POST /install/quick-setup → 409 duplicate_account →
next_action.tool === "oauth_fallback" → write tokenless .cursor/mcp.json]
A: Looks like you already have a Tracklution account. Enable the Tracklution
MCP and click Connect to sign in, then reply go.
U: go
A: [silent: get_status, list_containers -> bob's container for bobshop.com]
A: You're signed in. Add Tracklution to a NEW website, or connect to an
EXISTING container? Reply new <url> or existing.
U: existing
A: [silent: match bobshop.com, get_installation_scripts, apply,
verify_and_score -> ok, get_next_steps, create_login_link]
A: Tracking is live. Tracklution score: 78/100. Top 2 ways to reach 100%:
improve to walk through them one by one, or done to stop here.
### User chose `advanced` explicitly, then added a NEW website
U: Install Tracklution.
A: Tracklution needs your email and website URL. Reply with both (e.g.,
you@example.com mysite.com). Type advanced to use OAuth instead.
U: advanced
A: [silent: write tokenless .cursor/mcp.json]
A: I added a tokenless Tracklution MCP entry. Enable it in your editor and
click Connect to sign in, then reply go.
U: go
A: [silent: get_status, list_containers -> 1 container for othersite.com]
A: You're signed in. Add Tracklution to a NEW website, or connect to an
EXISTING container? Reply new <url> or existing.
U: new https://newsite.example.com
A: [silent: register_and_provision({auth_token, website_url, framework, agent_client, idempotency_key}),
get_installation_scripts, apply, verify_and_score -> only_pageview_seen,
get_next_steps, create_login_link]
A: Tracking is live. Tracklution score: 35/100. Top 3 ways to reach 100%:
improve to walk through the in-code changes, or done to stop.
### Multi-client ambiguous (advanced + new website + multi-company account)
U: Install Tracklution.
A: Tracklution needs your email and website URL. Reply with both (e.g.,
you@example.com mysite.com). Type advanced to use OAuth instead.
U: advanced
A: I added a tokenless Tracklution MCP entry. Enable it in your editor and
click Connect to sign in, then reply go.
U: go
A: [silent: get_status, list_containers -> 27 containers across 2 clients]
A: You're signed in. Add Tracklution to a NEW website, or connect to an
EXISTING container? Reply new <url> or existing.
U: new https://brand-new.example.com
A: [silent: register_and_provision -> HTTP 409 multi_client_ambiguous]
A: Multiple companies detected on this account. Open
https://app.tracklution.com and add the new container under the correct
company; then re-run with the resulting container_id.
## Magic install — one POST, one file write
The one HTTP request you make from inside the agent:
```http
POST https://api.trlution.com/install/quick-setup
Content-Type: application/json
{
"idempotency_key": "<fresh UUID-v7 or 16-128 chars [A-Za-z0-9_-]>",
"email": "<user email>",
"website_url": "<production URL>",
"framework": "html" | "nextjs",
"agent_client": "cursor" | "claude-code" | "codex" | "windsurf" | "cline"
}
Use api.trlution.com (short host) directly. api.tracklution.com
302-redirects here and that redirect silently drops POST bodies in several
HTTP clients (PowerShell Invoke-RestMethod, node fetch with
redirect: "manual", some agent host sandboxes).
The success response (HTTP 200) carries the four handles you need:
{"status": "ok","data": {"mcp_token": "<long JWT>","mcp_token_expires_at": "<ISO-8601>","container": { "id": "...", "hash": "...", "website_domain": "...", "tracking_domain": "..." },"mcp_config_snippet": {"tracklution": {"url": "https://mcp.tracklution.com/mcp","headers": { "Authorization": "Bearer <mcp_token>" }}},"mcp_endpoint": "https://mcp.tracklution.com/mcp"},"next_action": {"tool": "merge_mcp_config","args": { "target_path": ".cursor/mcp.json", "merge_key": "mcpServers.tracklution" },"reason": "Merge data.mcp_config_snippet, then follow agent-install.md turn 3 hand-off."}}
Save container.id AND container.hash to scratch state — every follow-up
onboarding-tool call needs both.
Per-host config file (the file you write data.mcp_config_snippet into):
| Host | File | Notes |
|---|---|---|
| Cursor | .cursor/mcp.json (project root) | Shallow-merge into mcpServers. 2-space indent. |
| Claude Code | n/a — use claude mcp add --transport http --header "Authorization: Bearer <jwt>" tracklution https://mcp.tracklution.com/mcp | Options must come BEFORE the server name. |
| Codex | ~/.codex/config.toml (POSIX) / %USERPROFILE%\.codex\config.toml (Windows) | TOML. Header table is http_headers NOT headers. Restart Codex after writing — no hot-reload. |
| Windsurf | ~/.codeium/windsurf/mcp_config.json | Accepts url or serverUrl. Full restart after write — no hot-reload. |
| Cline | platform-specific cline_mcp_settings.json (see reference doc) | type: "streamableHttp" required. |
Error handling — match on next_action.tool first, then errors[0].code:
next_action.tool === "oauth_fallback" (HTTP 409 duplicate_account) →
silent OAuth fallback. Write tokenless config. Send Turn 3 duplicate
hand-off. Do NOT explain the error.errors[0].code === "validation_failed" (HTTP 422) → re-prompt for the bad
field once. Most common: website_url is a localhost / IP literal.errors[0].code === "rate_limited" (HTTP 429) → respect
errors[0].details.retry_after_seconds. Tell user: "Rate-limited; try
again in N seconds."For the full error code table, see
agent-install-reference.md.
Write the tokenless MCP entry (same body as the magic snippet, but omit the
headers block). The host's UI then surfaces a Connect button (Cursor) or
its equivalent. User clicks it, completes browser OAuth, and reports back
with go. On go, run Turn 4 with the OAuth path of Step 2 — ask the
"new vs existing" branch question, then call list_containers (or
register_and_provision({auth_token, ...}) for the new branch), then
continue with Step 3.
If list_containers returns 0 containers, skip the branch question and go
straight to the new <url> path with auth_token + the website URL the
user supplied originally (or ask once for the URL if you don't have it).
Brand-new + advanced is fully supported — the server reads identity from
the JWT, so name / email arguments are NOT required.
If host detection fails, neither path completes, or any failure mode lands outside the recovery branches above, send once and stop:
Drop this into Cursor, Claude Code, Codex, Windsurf, Lovable, Replit, or Bolt. The agent reads our install protocol, mounts the Tracklution MCP, and finishes the setup.
Install Tracklution to this siteInstall Tracklution tracking — see https://www.tracklution.com/agent-install/The agent writes this into .cursor/mcp.json:
{
"mcpServers": {
"tracklution": {
"url": "https://mcp.tracklution.com/mcp"
}
}
}Server Side Tracking
Google Ads Server Side Tracking
Facebook Server Side Tracking Connector
TikTok Server Side Tracking
Shopify Server Side Tracking
GA4 Server Side Tracking
LinkedIn Server Side Tracking
Snapchat Server Side Tracking
WooCommerce Server Side Tracking
WordPress Server Side Tracking
Pinterest Conversions API
Microsoft (Bing) Server Side Tracking
Stripe Conversion Tracking


4.8+ Rating on G2 and Trustpilot
Helsinki
Simonkatu 6 A
00100 Helsinki, Finland
New York
307 W 38th St
NY 11105, United States



© Tracklution Oy 2026. All rights reserved.
GTM template with instructions video from Simo Ahava!