# MCP Ab Testing

> Splits contacts from a Leadspicker project into N equal random batches for A/B testing, through the Leadspicker MCP server. Use whenever the user wants to split, divide, or distribute contacts across multiple projects for testing — for example "A/B test my leads", "split contacts into two projects", "divide my project into 3 groups", "copy half the contacts to another project", "create test groups from my list", "AB test", "split do projektů", "rozděl kontakty", "rozděl projekt na části". Triggers on splitting or distributing contacts across Leadspicker projects for any experimental or campaign comparison purpose.

- Skill: `tomasblatak/mcp-ab-testing` (Agent Skill)
- Install (CLI): `npx skillmds@latest add tomasblatak/mcp-ab-testing`
- Raw SKILL.md: https://api.skillmd.com/api/skills/tomasblatak/mcp-ab-testing/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Marketing & Growth
- Author: tomasblatak (https://skillmd.com/u/tomasblatak)
- Updated: 2026-09-22
- Page: https://skillmd.com/skills/tomasblatak/mcp-ab-testing

---


# Leadspicker A/B Testing (MCP)

Splits all contacts from a source project into N equal random batches and distributes them
across N target projects — one batch per project. Use this for A/B (or A/B/C/…) testing
of outreach sequences, messaging variants, or campaign strategies.

Two modes:

1. **Auto-create mode** — target projects don't exist yet. The skill creates them as
   `{source_name}_version_a`, `_version_b`, … then deletes the original source project once
   the data is fully redistributed and verified.
2. **Existing-projects mode** — the user already has the targets. The skill copies N − 1
   batches into them; the source project keeps the Nth batch (others deleted from it),
   becoming one of the test groups itself.

---

## MCP tools used

| MCP tool                            | Purpose                                                                                                            |
| ----------------------------------- | ------------------------------------------------------------------------------------------------------------------ |
| `mcp__leadspicker__list_projects`   | Resolve project by name or list recent                                                                             |
| `mcp__leadspicker__get_project`     | Fetch source project name + timezone                                                                               |
| `mcp__leadspicker__create_project`  | Create a new versioned project (`name`, `timezone`)                                                                |
| `mcp__leadspicker__list_contacts`   | Page contacts (`page_size=100`) and verify counts (`page_size=1` to read `count`)                                  |
| `mcp__leadspicker__copy_contacts`   | Copy a batch of contacts to a target project (`project_id`, `target_project_id`, `person_ids`, `copy_mode="copy"`) |
| `mcp__leadspicker__delete_contacts` | Mass-delete contacts from the source project after copying (`project_id`, `person_ids`)                            |
| `mcp__leadspicker__manage_project`  | `action="delete"` to delete the source project (auto-create mode only)                                             |

---

## User input

| Parameter           | Description                                   | Example       |
| ------------------- | --------------------------------------------- | ------------- |
| `source_project_id` | Source Leadspicker project (resolved by name) | `29953`       |
| `num_splits`        | How many equal groups to create               | `2`, `3`, `4` |
| `projects_exist`    | Are the target projects already created?      | `yes` / `no`  |

### Project resolution

**Display format:** always render projects as **`{name} (#{id})`**.

1. **User named a project** → `mcp__leadspicker__list_projects(search_query="{name}", limit=5)`. Confirm; if multiple, ask which one.
2. **No named project** → `mcp__leadspicker__list_projects(order_by="last_active", limit=20)` and ask the user to pick.

Apply the same resolution for each target project when `projects_exist=yes`.

- **`projects_exist=yes`:** resolve `num_splits − 1` target projects (the source project becomes the final group). Example for a 3-way split: resolve 2 targets; source project = group C.
- **`projects_exist=no`:** ask for `timezone` for the new projects, or inherit from the source (`mcp__leadspicker__get_project(project_id=source_project_id)` → `timezone`).

---

## Mode A — auto-create projects

### 1) Fetch source project details

```
mcp__leadspicker__get_project(project_id=<source_project_id>)
```

Extract `name` and `timezone`.

### 2) Create target projects

For each split (a, b, c, …) up to `num_splits`:

```
mcp__leadspicker__create_project(
    name=f"{source_name}_version_a",
    timezone=source_timezone,
)
```

Record the returned project `id` for each.

Letter sequence: a, b, c, d, e, f, … (up to 26 splits, practically 2–4).

### 3) Split and copy

See **Split logic** and **Copy execution** below.

### 4) Delete the original source project

After all batches are individually verified:

```
mcp__leadspicker__manage_project(project_id=<source_project_id>, action="delete")
```

The original is no longer needed — all its data is redistributed.

---

## Mode B — existing projects

### 1) Validate target projects

For each target project the user named, resolve to an `id` and verify with
`mcp__leadspicker__get_project(project_id=X)`. Confirm the name back to the user.

### 2) Split and copy

Copy N − 1 batches to the N − 1 target projects. The source project keeps the final batch.

### 3) Clean up source project

After all copies are verified, mass-delete the contacts that were copied out:

```
mcp__leadspicker__delete_contacts(
    project_id=<source_project_id>,
    person_ids=[<all copied contact IDs>],
)
```

The source project keeps exactly one batch; each target project has exactly one batch.

---

## Split logic

### Fetch all contacts (paginated)

```
mcp__leadspicker__list_contacts(project_id=<source_project_id>, page=1, page_size=100)
```

Repeat with `page=2, 3, …` until the returned `items` array is shorter than `page_size`,
or `total_fetched >= count`. Collect all `id` values.

### Divide into batches

```
total = len(all_ids)
base  = total // num_splits
remainder = total % num_splits

# Shuffle randomly (no fixed seed)
random.shuffle(all_ids)

batches = []
cursor = 0
for i in range(num_splits):
    size = base + (1 if i < remainder else 0)
    batches.append(all_ids[cursor:cursor + size])
    cursor += size
```

Remainder contacts (when total doesn't divide evenly) go one each into the first batches.
Example: 335 contacts, 3 splits → batches of 112, 112, 111.

### Show the plan before executing

Present this to the user and get explicit approval:

```
Source project: {source_name} (#{source_id}) — {total} contacts

Split plan:
  Group A → {target_name_a} (#{id_a}) — {size_a} contacts
  Group B → {target_name_b} (#{id_b}) — {size_b} contacts
  …

After copy:
  [Auto-create]   The source project (#{source_id}) will be DELETED
  [Existing]      {size_n} contacts will be DELETED from the source project
                  (source keeps group {last_letter} with {size_n} contacts)

Proceed? (yes/no)
```

---

## Copy execution

For each batch (except the last batch in existing-projects mode, which stays in source):

```
mcp__leadspicker__copy_contacts(
    project_id=<source_project_id>,
    target_project_id=<target_id>,
    person_ids=[<batch_ids>],
    copy_mode="copy",
)
```

After each copy, verify the target project count before moving to the next batch:

```
mcp__leadspicker__list_contacts(project_id=<target_id>, page=1, page_size=1)
```

Use the returned `count`.

The copy is **asynchronous** — poll with a 3–5-second delay and retry up to 5 times until
`count` matches the expected batch size. If it never matches, stop and alert the user
before proceeding to the next batch or deleting the source project.

**Critical:** never delete (or mass-delete from) the source project until ALL batch copies
have been individually verified.

---

## Workflow

### Step 1 — collect input

Resolve the source project (see **Project resolution**) and collect `num_splits` and
whether target projects already exist.

### Step 2 — fetch source

- `mcp__leadspicker__get_project(project_id=<source_id>)` — name, timezone.
- `mcp__leadspicker__list_contacts(project_id=<source_id>, page=1, page_size=1)` — total `count`.

### Step 3 — set up target projects

- **Auto-create:** create all `num_splits` projects via `mcp__leadspicker__create_project`. Record their IDs.
- **Existing:** resolve `num_splits − 1` targets by name; verify each with `mcp__leadspicker__get_project`.

### Step 4 — calculate split and show plan

Shuffle, divide into batches, show the full plan (names, IDs, counts, cleanup action).

### Step 5 — get user approval

Do not execute anything until the user explicitly confirms.

### Step 6 — copy all batches

For each batch:

1. `mcp__leadspicker__copy_contacts(...)`.
2. Poll target count every 3–5 s, up to 5 attempts, until `count` equals expected batch size.
3. Only proceed to the next batch once confirmed.
4. If a copy fails to verify after 5 attempts, stop and alert the user — do not proceed.

Report each confirmed copy: `✓ Group A → {name} (#{id}): {count} contacts`.

### Step 7 — verify all target projects

Before any deletion, re-check `count` on every target project one final time. If anything
looks off, stop and alert the user.

**Do not proceed to Step 8 until every target project is confirmed.**

### Step 8 — cleanup (only after Step 7 passes)

- **Auto-create:** `mcp__leadspicker__manage_project(project_id=<source_id>, action="delete")`.
- **Existing:** `mcp__leadspicker__delete_contacts(project_id=<source_id>, person_ids=[<all copied IDs>])`. Then verify the source's remaining `count` equals one batch.

### Step 9 — report summary

```
A/B split complete ✓

| Group | Project name        | Project ID | Contacts |
|-------|---------------------|------------|----------|
| A     | {name}_version_a    | {id}       | {count}  |
| B     | {name}_version_b    | {id}       | {count}  |
| …     | …                   | …          | …        |

Original project: DELETED (auto-create mode)
                  {count} contacts retained as Group {X} (existing-projects mode)
```

---

## Common errors

| Symptom                      | Likely cause                | Fix                                                                   |
| ---------------------------- | --------------------------- | --------------------------------------------------------------------- |
| Tool returns `404`           | Wrong project               | Re-list and reconfirm                                                 |
| Tool returns `422` on copy   | Required field missing      | `copy_contacts` needs `target_project_id` and `person_ids`            |
| Tool returns `409` on create | Project name already exists | Append `_2` to the version name or ask user for a different base name |
| Tool returns `429`           | Rate limit                  | Add 1–2 s delay between calls                                         |

---

## Safety

- Always show the full plan (including cleanup / deletion) and get explicit approval before executing.
- Project deletion is irreversible — verify all batches first.
- Never delete or mass-delete from the source project until every target batch is individually verified.

