# Tracklution Agent Install Contract

> 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`](https://www.tracklution.com/agent-install-reference.md).
Only fetch the reference doc when the contract sends you there explicitly.

## Silence rules

1. Do not narrate steps. No *"I'll now POST to..."*, no *"Let me detect your
   framework..."*, no *"Got it, running magic install..."*.
2. Do not echo URLs, JWTs, container IDs, hashes, idempotency keys, JSON
   envelopes, HTTP request bodies, file contents, or framework detection logic
   to the user. **Exception**: the single `create_login_link` URL in the
   Turn 5 scored hand-off message IS allowed and required.
3. Do not list per-host setup blocks for hosts you're not running in. Pick the
   one host that matches your environment in Turn 0 and stay there.
4. Do not paraphrase this document. The verbatim templates below are the only
   user-facing strings; everything else is for you.
5. Do not surface API error codes (`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.
6. ≤2 sentences per intermediate-turn agent message. Turn 1, the Turn 4
   Step 2 OAuth-branch question, the **Turn 5 scored hand-off**, and the
   **Turn 6 propose-diff prompt** all use explicit verbatim multi-line
   templates and are exempt from the cap.
7. After `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.

## Turn 0 — pre-flight (silent)

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:**

- **File-edit hosts** (Cursor, Codex, Windsurf, Cline): you can write the host's
  MCP-config file directly. Magic install works.
- **CLI host** (Claude Code): you can run `claude mcp add ...`. Magic install
  works.
- **User-action hosts** (Lovable, Replit, Bolt): you cannot edit MCP config
  from inside the agent. Skip magic install entirely; go straight to the
  user-action OAuth fallback (see Turn 3).

## Turn 1 — ask once

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`."*

## Turn 2 — branch (silent)

| 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.                                            |

## Turn 3 — hand-off (verbatim, host-aware)

Pick the single hand-off line that matches your branch and host class. Send
only that line. No preamble, no postscript.

- **Magic install + file-edit host** (Cursor / Codex / Windsurf / Cline):
  > I added the Tracklution MCP. Enable it in your editor's MCP settings, then
  > reply `go`.
- **Magic install + Claude Code CLI**:
  > I added the Tracklution MCP via the Claude CLI. Run `/mcp` to confirm it's
  > connected, then reply `go`.
- **OAuth, user-explicit `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`.
- **OAuth, duplicate-account fallback** (file-edit host):
  > Looks like you already have a Tracklution account. Enable the Tracklution
  > MCP and click Connect to sign in, then reply `go`.
- **User-action host** (Lovable / Replit / Bolt): use the host's `instruction`
  string from
  [`https://www.tracklution.com/api/install-recipes/`](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` ``.

## Turn 4 — on `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.

### Step 1 — verify MCP reachability

Call **`get_status`** (MCP). Success predicate:
`structuredContent.servers[].reachable === true`.

If `get_status` fails, recover per the failure mode:

- **401 + `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.
- **Tool not registered / "no MCP server found"** (session-snapshot stale
  — Cursor's known mid-conversation reload race): say once, then STOP:
  > 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`.

### Step 2 — resolve `container_id` + `container_hash`

Two branches; pick one based on how you got here:

- **Magic install path**: you already saved `data.container.{id, hash}` from
  the quick-setup response. Skip to Step 3.
- **OAuth path** (user typed `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.

#### Step 2.1 — OAuth branch question (verbatim)

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).

#### Step 2.2 — branch on the reply

- `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.

#### Step 2.3 — handle `multi_client_ambiguous`

If `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.

### Step 3 — fetch tracking snippets

Call **`get_installation_scripts({ container_id, container_hash, framework })`**.
Every onboarding-tool call MUST include `container_hash`.

### Step 4 — apply snippets

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.

### Step 5 — verify and route by `not_ready_reason` class

Call **`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.

## Turn 5 — scored hand-off (verbatim, multi-line)

After Turn 4 Step 5 routes here, run these three calls silently:

1. **`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`).
2. **`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.
3. Take the top 3 entries from `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:
>
> 1. {next_steps[0].title} — {next_steps[0].score_impact}
> 2. {next_steps[1].title} — {next_steps[1].score_impact}
> 3. {next_steps[2].title} — {next_steps[2].score_impact}
>
> 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.
- When Step 5 routed here on a non-`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.
- When Step 5 routed here after a budget-exhausted transient retry, lead
  with `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.

## Turn 6 — improve-score loop (on user `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):

1. Call **`get_next_steps({ container_id, container_hash, recalculate: false })`**.
2. Pick the first entry in `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.
3. Scan the project for the right surface for the picked step:
   - `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*`.
4. If the picked step suggests a snippet (use the matching
   `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.
5. Send this verbatim propose-diff prompt:
   > Found `<path>:<line>`. I'd add this snippet for **{step.score_impact}**:
   >
   > ```<lang>
   > <snippet>
   > ```
   >
   > Apply? Reply `yes` / `skip` / `show another`.
6. Branch on the reply:
   - `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.

## Example dialogues

### Happy path (Cursor, new user, Next.js, only PageView received on day 0)

```
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%:
   1. Activate your Google Ads connector — +25% on this step
   2. Enable First-Party Mode (one DNS record) — +15% on this step
   Open your dashboard: https://app.tracklution.com/login/abc124
   Reply `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%:
   1. Enable First-Party Mode (one DNS record) — +15% on this step
   2. Wire ContactInfo on your signup form — +7% on this step
   Open your dashboard: https://app.tracklution.com/login/def456
   Reply `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%:
   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/ghi789
   Reply `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:

```jsonc
{
  "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."*
- HTTP 5xx → failure clause.

For the full error code table, see
[`agent-install-reference.md`](https://www.tracklution.com/agent-install-reference.md#error-code-table).

## OAuth fallback

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.

## On failure

If host detection fails, neither path completes, or any failure mode lands
outside the recovery branches above, send once and stop:

> I can't install Tracklution automatically here. Sign up at
> https://www.tracklution.com/start.

## Reference

- Full per-host detail, error code table, two-axis model, file-safety notes:
  <https://www.tracklution.com/agent-install-reference.md>
- Service directory: <https://www.tracklution.com/.well-known/tracklution.json>
- Install recipes (machine-readable): <https://www.tracklution.com/api/install-recipes/>
- MCP server: <https://mcp.tracklution.com/mcp>
- Magic-install REST bootstrap: <https://api.trlution.com/install/quick-setup>
- Knowledge base: <https://www.tracklution.com/docs/>
- Support: <support@tracklution.com>
