Skip to main content
Glama

Track prospects

track_prospects

A person belongs to exactly one campaign, so someone already tracked in another campaign is reported in skipped rather than added twice; pass move_existing=True to move them into this agent instead. A durable contact left without a campaign (its campaign was deleted) is instead adopted straight in and reported in adopted. Someone already on THIS campaign who was removed from it is reported in removed, not re-enrolled and not sent to. If the user explicitly wants them back, restore them with update_prospect(agent_id=<this agent>, prospect_id=<their id from query_prospects>, channel=<'email' or 'linkedin', one they have a contact on>, stage='pending'). Someone with no usable identifier (no LinkedIn profile URL, username slug, provider ID or email — say, only a company page URL) is reported in unrecognized and not added; the rest of the batch is.

On an agent with a define_sequence flow, tracking IS enrollment — the flow fires each prospect's first touch itself (see the returned flow field); never queue a first send for prospects you just tracked. Dict with created and updated counts; moved, adopted, removed, skipped and unrecognized details, plus unrecognized_note naming anyone not added; and total, the number of items.

Tool call template

Replace each placeholder with a value matching the input schema. Only required parameters are included; add optional parameters as needed.

{
  "id": 1,
  "jsonrpc": "2.0",
  "method": "tools/call",
  "params": {
    "arguments": {
      "agent_id": "<agent_id>",
      "items": "<items>"
    },
    "name": "track_prospects"
  }
}

Input Schema

NameRequiredDescriptionDefault
itemsYesPeople to add (see ProspectItem). Each carries a `person` with the identifiers you already hold (`linkedin_slug`, `linkedin_provider_id`, or `email`) copied verbatim from the tool result that surfaced them — never a slug rebuilt from a display name, which resolves to the wrong person. The display name goes on `person.display_name`, taken from the same row as the identifier — a name that shares no token with the LinkedIn name already on file for its ID rejects the whole call.
agent_idYesID of the agent to add prospects to
on_behalf_ofNoA teammate's email, or an agency client's from list_agency_clients, to run this call in that account: their agents, settings, connected accounts, and credits. Omit it to act on your own account.
move_existingNoMove people tracked in another campaign into this one instead of skipping them. Destructive — it cancels whatever was still queued for them, so only pass it on an explicit user request.

Schema Changelog

Changes observed during successful MCP inspections.

  1. Changed1 schema field changed
    • changedInput schema / properties / on_behalf_of / description
      Previous value: -"An agency client's email, from list_agency_clients, to run this call in that client's account: their agents, settings, connected accounts, and credits. Omit it to act on your own account."New value: +"A teammate's email, or an agency client's from list_agency_clients, to run this call in that account: their agents, settings, connected accounts, and credits. Omit it to act on your own account."
  2. Changed4 schema fields changed
    • changedInput schema / properties / items / description
      Previous value: -"People to add (see ProspectItem). Each carries a `person` with the\nidentifiers you already hold (LinkedIn URL / slug, email, or\nprovider_id) copied verbatim from the tool result that surfaced them —\nnever a slug rebuilt from a display name, which resolves to the wrong\nperson. The display name goes on `person.display_name`, taken from the\nsame row as the identifier — a name that shares no token with the\nLinkedIn name already on file for its ID rejects the whole call."New value: +"People to add (see ProspectItem). Each carries a `person` with the\nidentifiers you already hold (`linkedin_slug`, `linkedin_provider_id`, or\n`email`) copied verbatim from the tool result that surfaced them —\nnever a slug rebuilt from a display name, which resolves to the wrong\nperson. The display name goes on `person.display_name`, taken from the\nsame row as the identifier — a name that shares no token with the\nLinkedIn name already on file for its ID rejects the whole call."
    • changedInput schema / properties / items / items / properties / person / description
      Previous value: -"The person to track. Pass the identifiers you already hold (LinkedIn URL / slug, provider_id, email) copied verbatim from the tool result that surfaced them — never a slug rebuilt from a display name, which resolves to a different real person. A company page URL, free text, or 'N/A' isn't an identifier: a person with nothing else isn't added and comes back in `unrecognized`. A bare company domain such as acme.com, by contrast, rejects the whole call."New value: +"The person to track. Pass the identifiers you already hold (`linkedin_slug`, `linkedin_provider_id`, `email`) copied verbatim from the tool result that surfaced them — never a slug rebuilt from a display name, which resolves to a different real person. A company page URL, free text, or 'N/A' isn't an identifier: a person with nothing else isn't added and comes back in `unrecognized`. A bare company domain such as acme.com in `linkedin_slug`, by contrast, rejects the whole call."
    • addedInput schema / properties / items / items / properties / person / properties / linkedin_slug
      Added value: +{
      +  "default": "",
      +  "description": "The lead's LinkedIn profile slug or profile URL, if known.",
      +  "type": "string"
      +}
    • removedInput schema / properties / items / items / properties / person / properties / linkedin_url
      Removed value: -{
      -  "default": "",
      -  "description": "The lead's LinkedIn profile URL, if known.",
      -  "type": "string"
      -}
  3. Changed5 schema fields changed
    • changedInput schema / properties / items / description
      Previous value: -"People to add (see ProspectItem). Each carries a `person` with the\nidentifiers you already hold (LinkedIn URL / slug, email, or\nprovider_id) copied verbatim from the tool result that surfaced them —\nnever a slug rebuilt from a display name, which resolves to the wrong\nperson. The display name goes on `person.display_name`, taken from the\nsame row as the identifier — a name that shares no token with the\nLinkedIn name already on file for its ID rejects the whole call.\nOptional freeform `data` per item."New value: +"People to add (see ProspectItem). Each carries a `person` with the\nidentifiers you already hold (LinkedIn URL / slug, email, or\nprovider_id) copied verbatim from the tool result that surfaced them —\nnever a slug rebuilt from a display name, which resolves to the wrong\nperson. The display name goes on `person.display_name`, taken from the\nsame row as the identifier — a name that shares no token with the\nLinkedIn name already on file for its ID rejects the whole call."
    • changedInput schema / properties / items / items / description
      Previous value: -"One person to add via track_prospects. `person` carries the identifiers the\nlead was surfaced with; `data` is freeform attributes stored on the prospect\nrow. Unknown top-level keys are rejected (extra='forbid')."New value: +"One person to add via track_prospects. `person` carries the identifiers the\nlead was surfaced with. Unknown top-level keys are rejected (extra='forbid')."
    • changedInput schema / properties / items / items / properties / data / description
      Previous value: -"Freeform person attributes stored on the prospect row's data blob. When the person was sourced from a company signal (a job posting, a news event), include signal — the company signal's category label, e.g. 'Hiring for Founding AE' or 'Startup Funding' — and signal_url, its source URL: prospect views render them as why the person was added."New value: +"Person attributes stored on the prospect's row.\nKeys beyond the named fields are stored as given."
    • addedInput schema / properties / items / items / properties / data / properties
      Added value: +{
      +  "company": {
      +    "anyOf": [
      +      {
      +        "type": "string"
      +      },
      +      {
      +        "type": "null"
      +      }
      +    ],
      +    "default": null,
      +    "description": "The person's company name as plain text, e.g. 'Acme Corp' — the name alone, not the {name, website} object record_search_results takes."
      +  },
      +  "headline": {
      +    "anyOf": [
      +      {
      +        "type": "string"
      +      },
      +      {
      +        "type": "null"
      +      }
      +    ],
      +    "default": null
      +  },
      +  "location": {
      +    "anyOf": [
      +      {
      +        "type": "string"
      +      },
      +      {
      +        "type": "null"
      +      }
      +    ],
      +    "default": null
      +  },
      +  "signal": {
      +    "anyOf": [
      +      {
      +        "type": "string"
      +      },
      +      {
      +        "type": "null"
      +      }
      +    ],
      +    "default": null,
      +    "description": "When the person was sourced from a company signal (a job posting, a news event), set this to the signal's category label, e.g. 'Hiring for Founding AE' or 'Startup Funding', and signal_url to its source URL: prospect views render them as why the person was added."
      +  },
      +  "signal_url": {
      +    "anyOf": [
      +      {
      +        "type": "string"
      +      },
      +      {
      +        "type": "null"
      +      }
      +    ],
      +    "default": null
      +  },
      +  "title": {
      +    "anyOf": [
      +      {
      +        "type": "string"
      +      },
      +      {
      +        "type": "null"
      +      }
      +    ],
      +    "default": null
      +  }
      +}
    • addedInput schema / properties / items / items / properties / data / title
      Added value: +"ProspectData"
  4. Changed1 schema field changed
    • addedInput schema / properties / on_behalf_of
      Added value: +{
      +  "anyOf": [
      +    {
      +      "type": "string"
      +    },
      +    {
      +      "type": "null"
      +    }
      +  ],
      +  "default": null,
      +  "description": "An agency client's email, from list_agency_clients, to run this call in that client's account: their agents, settings, connected accounts, and credits. Omit it to act on your own account."
      +}
  5. Changed2 schema fields changed
    • changedInput schema / properties / items / description
      Previous value: -"People to add (see ProspectItem). Each carries a `person` with the\nidentifiers you already hold (LinkedIn URL / slug, email, or\nprovider_id) copied verbatim from the tool result that surfaced them —\nnever a slug rebuilt from a display name, which resolves to the wrong\nperson. Junk like a company URL or 'N/A' is rejected. The display name\ngoes on `person.display_name`, taken from the same row as the identifier\n— a name that shares no token with the LinkedIn name already on file for\nits ID rejects the whole call. Optional freeform `data` per item."New value: +"People to add (see ProspectItem). Each carries a `person` with the\nidentifiers you already hold (LinkedIn URL / slug, email, or\nprovider_id) copied verbatim from the tool result that surfaced them —\nnever a slug rebuilt from a display name, which resolves to the wrong\nperson. The display name goes on `person.display_name`, taken from the\nsame row as the identifier — a name that shares no token with the\nLinkedIn name already on file for its ID rejects the whole call.\nOptional freeform `data` per item."
    • changedInput schema / properties / items / items / properties / person / description
      Previous value: -"The person to track. Pass the identifiers you already hold (LinkedIn URL / slug, provider_id, email) copied verbatim from the tool result that surfaced them — never a slug rebuilt from a display name, which resolves to a different real person. A company URL, domain, free text, or 'N/A' is rejected."New value: +"The person to track. Pass the identifiers you already hold (LinkedIn URL / slug, provider_id, email) copied verbatim from the tool result that surfaced them — never a slug rebuilt from a display name, which resolves to a different real person. A company page URL, free text, or 'N/A' isn't an identifier: a person with nothing else isn't added and comes back in `unrecognized`. A bare company domain such as acme.com, by contrast, rejects the whole call."
  6. Changed1 schema field changed
    • changedInput schema / properties / items / description
      Previous value: -"People to add (see ProspectItem). Each carries a `person` with the\nidentifiers you already hold (LinkedIn URL / slug, email, or\nprovider_id) copied verbatim from the tool result that surfaced them —\nnever a slug rebuilt from a display name, which resolves to the wrong\nperson. Junk like a company URL or 'N/A' is rejected. The display name\ngoes on `person.display_name`; optional freeform `data` per item."New value: +"People to add (see ProspectItem). Each carries a `person` with the\nidentifiers you already hold (LinkedIn URL / slug, email, or\nprovider_id) copied verbatim from the tool result that surfaced them —\nnever a slug rebuilt from a display name, which resolves to the wrong\nperson. Junk like a company URL or 'N/A' is rejected. The display name\ngoes on `person.display_name`, taken from the same row as the identifier\n— a name that shares no token with the LinkedIn name already on file for\nits ID rejects the whole call. Optional freeform `data` per item."
  7. Changed3 schema fields changed
    • addedInput schema / properties / agent_id
      Added value: +{
      +  "description": "ID of the agent to add prospects to",
      +  "type": "integer"
      +}
    • removedInput schema / properties / task_id
      Removed value: -{
      -  "description": "ID of the task to add prospects to",
      -  "type": "integer"
      -}
    • changedInput schema / required
      Previous value: -[
      -  "task_id",
      -  "items"
      -]New value: +[
      +  "agent_id",
      +  "items"
      +]
  8. First observed

TDQS

A4.6/5.0
Behavior4/5

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

The description adds substantial behavior beyond the annotations: it explains campaign-exclusive dedup, the `skipped`/`adopted`/`removed`/`unrecognized` buckets, and that tracking is enrollment on a `define_sequence` flow. The main description does not itself state that `move_existing` cancels queued sends, though that is documented in the schema's parameter description.

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 text is long but front-loaded and organized around the main action, the sibling alternative, and then the edge-case buckets. Some identifier rules are repeated between the main description and schema, and the full `update_prospect` restoration call is verbose, but the length is mostly justified by the tool's complexity.

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 bulk mutation tool with no output schema, the definition is unusually complete: it describes return fields in the `<returns>` block, covers dedup and partial-batch behavior, and warns about flow-driven first touches. An agent has enough information to call it correctly and interpret the result.

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 schema already carries the parameter details. The description still adds meaningful semantics by explaining why `move_existing` matters in campaign membership and how identifier quality affects whether a person is added or returned in `unrecognized`.

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?

The description states a specific verb and resource: bulk-add people to an agent's outreach list with cross-agent dedup. It also explicitly distinguishes this tool from the sibling `track_monitored_companies` for company monitoring, so an agent can route correctly without opening either 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?

It gives explicit when-to-use guidance, names the alternative for companies, and explains the conditions around `move_existing`, restoration via `update_prospect`, and not queueing a first send when a `define_sequence` flow is active. These are practical routing and eligibility rules, not just implied usage.

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