Skip to main content
Glama

Move A Contact Between Campaigns

li_move_contact
Destructive

Move people's membership from one campaign to another, keeping their invite and message history — nothing is re-created, so the sequence continues where it was. This is how you correct a batch that landed in the wrong campaign; before it existed the only option was to stop every person and re-import them, which loses their history. Both campaigns must send from the SAME account: a campaign is bound to one LinkedIn login, so moving across accounts would change who is approaching this person mid-sequence. Refused if the target already holds them (one membership per campaign) or is completed. Their scheduled steps belong to the target campaign afterwards. ⚠️ Moving people into a PAUSED campaign holds further INVITES but does NOT hold a message step that is already scheduled — nothing checks the campaign at send time for those. Three forms: one person, a list of profileUrls, or allInCampaign for everyone still in the source, which is the shape a mis-filed import actually has. A list refuses outright if some cannot move rather than splitting the group across two campaigns; allInCampaign previews first and needs the count echoed back before it writes.

MOVE AND RE-COPY in one call: resnapshot=true (or stepTexts) first re-copies each mover's step kinds and delays from the TARGET campaign's strategy, keeping their own text, then moves them. Only for people nothing has happened to yet (invite not sent, no step sent or scheduled); anyone else, or anyone with no text for a target step kind, is named and nothing is moved. Calling again after a failure is safe.

idempotencyKey: pass the same key when retrying after a timeout, 502 or 503; a call that already finished returns its stored answer instead of running twice (24h).

Input Schema

NameRequiredDescriptionDefault
dryRunNoPreview the move (and the re-copy) without writing.
partialNoMove the ones that can move even though some cannot. Default false.
contactIdNoOne contact by id, instead of profileUrl
stepTextsNoPer-person text for target step kinds a mover has no text for (or to replace theirs). Implies resnapshot.
profileUrlNoOne person
resnapshotNoRe-copy each mover's steps from the target campaign's strategy before moving (kinds and delays; their own text kept). Untouched people only. Not with contactId.
expectCountNoHow many you expect to move, from the preview. The call is refused if the campaign holds a different number — which is what stops a mistyped fromCampaignId moving a live campaign wholesale.
profileUrlsNoSeveral people in ONE call, max 500. Every one is checked BEFORE anything is written: if some cannot move the whole call is refused, because moving the rest would split the group across two campaigns — which is the problem you are fixing, made harder to see. Pass partial=true to move the ones that can.
toCampaignIdYesWhere they should be instead
allInCampaignNoMove EVERYONE still in the source campaign. The shape a mis-filed import actually has, since nobody holds a list of them. Returns a PREVIEW first and writes nothing until you call again with expectCount set to the number it showed.
fromCampaignIdYesThe campaign they are in now
idempotencyKeyNoAny unique string for THIS request (e.g. a UUID). Reuse it only to retry the same call.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed4 schema fields changed
    • addedInput schema / properties / dryRun
      Added value: +{
      +  "description": "Preview the move (and the re-copy) without writing.",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / idempotencyKey
      Added value: +{
      +  "description": "Any unique string for THIS request (e.g. a UUID). Reuse it only to retry the same call.",
      +  "maxLength": 200,
      +  "minLength": 8,
      +  "type": "string"
      +}
    • addedInput schema / properties / resnapshot
      Added value: +{
      +  "description": "Re-copy each mover's steps from the target campaign's strategy before moving (kinds and delays; their own text kept). Untouched people only. Not with contactId.",
      +  "type": "boolean"
      +}
    • addedInput schema / properties / stepTexts
      Added value: +{
      +  "description": "Per-person text for target step kinds a mover has no text for (or to replace theirs). Implies resnapshot.",
      +  "items": {
      +    "additionalProperties": false,
      +    "properties": {
      +      "closer": {
      +        "description": "Text for the \"closer\" step",
      +        "maxLength": 8000,
      +        "type": "string"
      +      },
      +      "firstMessage": {
      +        "description": "Text for the \"first\" step",
      +        "maxLength": 8000,
      +        "type": "string"
      +      },
      +      "followUp1": {
      +        "description": "Text for the \"followup_1\" step",
      +        "maxLength": 8000,
      +        "type": "string"
      +      },
      +      "followUp2": {
      +        "description": "Text for the \"followup_2\" step",
      +        "maxLength": 8000,
      +        "type": "string"
      +      },
      +      "followUp3": {
      +        "description": "Text for the \"followup_3\" step",
      +        "maxLength": 8000,
      +        "type": "string"
      +      },
      +      "profileUrl": {
      +        "description": "The person, as stored or as any spelling of them",
      +        "minLength": 1,
      +        "type": "string"
      +      }
      +    },
      +    "required": [
      +      "profileUrl"
      +    ],
      +    "type": "object"
      +  },
      +  "maxItems": 500,
      +  "type": "array"
      +}
  2. Changed1 schema field changed
    • addedInput schema / properties / contactId / description
      Added value: +"One contact by id, instead of profileUrl"
  3. First observed

TDQS

A4.8/5.0
Behavior5/5

Does the description disclose side effects, auth requirements, rate limits, or destructive behavior?

Annotations declare destructiveHint=true, but the description adds far more than the safety flag: history is preserved rather than re-created, cross-account moves are refused because a campaign is bound to one login, the paused-campaign caveat that invites are held but already-scheduled message steps are not, partial-vs-atomic list semantics, and the idempotency key's 24h replay window. This is genuinely non-obvious behavioral context.

Agents need to know what a tool does to the world before calling it. Descriptions should go beyond structured annotations to explain consequences.

Conciseness4/5

Is the description appropriately sized, front-loaded, and free of redundancy?

The purpose is front-loaded and every paragraph carries decision-relevant information (eligibility, atomicity, preview flow, idempotency). It is long and some content overlaps the schema descriptions, but there is little that is purely decorative; the emoji warning marks a real trap.

Shorter descriptions cost fewer tokens and are easier for agents to parse. Every sentence should earn its place.

Completeness5/5

Given the tool's complexity, does the description cover enough for an agent to succeed on first attempt?

For a 12-parameter destructive tool with no output schema, the description covers what an agent needs: which parameter shape to choose, what gets previewed vs written, what causes refusal, and how retries are deduplicated. Nothing essential to calling it correctly is missing.

Complex tools with many parameters or behaviors need more documentation. Simple tools need less. This dimension scales expectations accordingly.

Parameters4/5

Does the description clarify parameter syntax, constraints, interactions, or defaults beyond what the schema provides?

Schema coverage is 100%, so the baseline is 3, but the description adds interaction-level meaning the schema does not: how resnapshot/stepTexts pair with the move, why expectCount exists (guarding a mistyped fromCampaignId), and why a list refuses wholesale while allInCampaign previews first. It does not add syntax detail beyond the schema, keeping it from a 5.

Input schemas describe structure but not intent. Descriptions should explain non-obvious parameter relationships and valid value ranges.

Purpose5/5

Does the description clearly state what the tool does and how it differs from similar tools?

States a specific verb and resource — moving membership between campaigns — and immediately scopes the behavior ('keeping their invite and message history — nothing is re-created'). It contrasts itself against the historical alternative ('stop every person and re-import them'), so an agent can distinguish it from siblings like li_reset_contact or li_import_prospects without opening any schema.

Agents choose between tools based on descriptions. A clear purpose with a specific verb and resource helps agents select the right tool.

Usage Guidelines5/5

Does the description explain when to use this tool, when not to, or what alternatives exist?

Explicit when-to-use ('how you correct a batch that landed in the wrong campaign'), plus hard preconditions (both campaigns must send from the SAME account) and refusal conditions (target already holds them, or is completed). It also disambiguates the three invocation shapes and tells the agent when to use each, which is exactly the routing information a caller needs.

Agents often have multiple tools that could apply. Explicit usage guidance like "use X instead of Y when Z" prevents misuse.

Try in Browser

Glama MCP Gateway

Add one secure layer between your agents and this server.

Resources