Skip to main content
Glama

Queue LinkedIn outreach

queue_linkedin_outreach
Destructive

A campaign already sending without a flow keeps calling this tool without node_id, as do a one-off send outside a campaign and a resolve look-up. Every other campaign sends through its flow, even a single message or connection requests with nothing after: create it with define_sequence and add the prospects with track_prospects, and the flow's steps call this tool with their node_id. A call without node_id on an agent that has no flow and hasn't sent any outreach yet is refused.

Only call this AFTER the user has explicitly confirmed the outreach.

BATCH: pass ALL recipients in a single call. The tool is designed to batch — provider_id recipients queue directly (resolving before send unless already verified) and URL/slug recipients route through the background resolving queue with rate-limited, business-hours pacing. Calling once per recipient multiplies prompt overhead by ~N and produces no observable benefit. If you have 27 prospects to queue, that's ONE call with recipients of length 27, not 27 calls of length 1. Dict with known_queued (provider_id recipients queued directly), deferred_count (URL/slug recipients queued for background resolution), awaiting_approval (how many of those the user's approval settings hold until the user approves them in Sliq — tell the user these wait on their approval; only the rest are on their way. A row whose profile is still being looked up counts by those settings and reaches the approval queue once its lookup finishes; the lookup can also hold a row the count left out, when it shows the user already talks with that person on LinkedIn), tracking_skipped (recipients already tracked in another campaign, so nothing queued for them — each entry names existing_agent_title / existing_agent_id; surface these and let the user place them), skipped (recipients the queue helper refused — each entry has target_name, provider_id, reason, error, plus existing_agent_id / existing_agent_title when a row on another campaign is what blocked the send; name that campaign to the user rather than calling it a duplicate on this one), accept_followup (present only when connection requests queued to this agent but no sequence node or accept trigger will fire a message on acceptance — names how to wire one), and rate_limit_estimate — an ETA for the batch covering expected profile resolution and sending days given the user's current rate-limit budget. Its summary names the rows awaiting approval and gives the ETA for the rest only.

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": {
      "recipients": "<recipients>",
      "action_type": "<action_type>"
    },
    "name": "queue_linkedin_outreach"
  }
}

Input Schema

NameRequiredDescriptionDefault
enrichNoWhen True, look up each prospect's verified work email via Apollo and stamp it on the tracking row. Requires each recipient to have `target_name` plus a `linkedin_slug` (a profile slug or URL). Costs 1 Sliq credit per verified hit (a zero Sliq balance skips the whole lookup); free with BYO Apollo. Recipients missing inputs are silently skipped.
notifyNoOnly for action_type='resolve'. False (default) = a silent data lookup the person is NOT notified of. True = a visible "View Profile" visit that DOES notify them ("X viewed your profile") — a no-message warm-up touch, typically placed later in a sequence, not a data read. Same paced lookup and daily cap either way. Rejected on non-resolve actions. On a sequenced call the resolve node's own `notify` field is authoritative and is OR'd in, so a "View Profile" node always visits.
node_idNoOptional. The id of the sequence-DAG node this batch enacts (a node in the agent's `sequence`, authored via `define_sequence`). Stamped on every queued row so the Campaign Flow view can place each prospect and count per node. Pass it whenever the agent has a sequence. Rejected with ModelRetry if it isn't a node in the agent's sequence.
agent_idNoOptional. Pass for campaigns or when inside an existing agent. Omit for ad hoc sends — the tool auto-uses the "LinkedIn Quick Actions" default agent. Every send lands in the agent you name; recipients already tracked in a different one are reported in `tracking_skipped` rather than queued (see Returns).
recipientsYesList of recipient dicts. Each dict must carry one of `linkedin_slug` (a profile slug or URL), `provider_id`, or `chat_id`, copied verbatim from the tool result that surfaced the person — never a slug rebuilt from a display name, which enrolls the wrong person. Other attributes go under `data`; unknown top-level keys are rejected. If you have no identifier for someone, omit them rather than guess. When a recipient's `target_name` shares no tokens with any LinkedIn name already held for that `provider_id` (a verified prospect or a post engager), the whole batch is rejected with ModelRetry before anything queues — fix the pairing and re-send (use the person's LinkedIn URL/identifier if the pid is wrong; use one consistent name if it's the same person). A pid with no held name isn't gated here — it resolves before send and the resolver adjudicates the name then.
action_typeYes'resolve', 'connection_request', 'message', 'inmail', or 'follow'. For 'resolve' and 'follow', the `message` field is unused and rejected with ModelRetry. Follow auto-sends by default (its own `linkedin_follow` approval subtype); gate it via outbound-approval settings. 'resolve' commits NO outreach — it queues a paced profile lookup that fires a `resolved_profile` trigger event per person; use it when the per-person action depends on looked-up data (see the trigger-code skill for the branching example). Pair with `notify=True` for a visible "View Profile" visit (see `notify`). The event carries the looked-up profile — `already_connected`, `network_distance`, `connections_count`, `follower_count`, `headline`, `title`, `company` — and the same fields land on the prospect's row under `data`, so a later turn reads them with `query_prospects` instead of resolving again. A connection-count rule can read `connections_count` from here; when the rule turns on the count alone, `enrich_linkedin_profiles` returns it for the whole list in one call without spending account budget, so filter there first and queue only the recipients that pass.
linkedin_apiNoFor inmail only. Leave unset — the tool bills the InMail against the pool for the LinkedIn plan Sliq recorded when LinkedIn was connected. Only pass an explicit 'classic', 'sales_navigator', or 'recruiter' to force a pool.
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.

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. Changed6 schema fields changed
    • changedInput schema / properties / enrich / description
      Previous value: -"When True, look up each prospect's verified work email via\nApollo and stamp it on the tracking row. Requires each recipient\nto have `target_name` plus a `linkedin_url` (a profile URL or slug).\nCosts 1 Sliq credit per verified hit (a zero Sliq balance skips the\nwhole lookup); free with BYO Apollo. Recipients missing inputs are\nsilently skipped."New value: +"When True, look up each prospect's verified work email via\nApollo and stamp it on the tracking row. Requires each recipient\nto have `target_name` plus a `linkedin_slug` (a profile slug or URL).\nCosts 1 Sliq credit per verified hit (a zero Sliq balance skips the\nwhole lookup); free with BYO Apollo. Recipients missing inputs are\nsilently skipped."
    • changedInput schema / properties / recipients / description
      Previous value: -"List of recipient dicts. Each dict must carry one of\n`linkedin_url` (a profile URL or slug), `provider_id`, or `chat_id`,\ncopied verbatim from the tool result that surfaced the person — never a\nslug rebuilt from a display name, which enrolls the wrong person.\nOther attributes go under `data`; unknown top-level keys are\nrejected. If you have no identifier for someone, omit them rather than\nguess. When a recipient's\n`target_name` shares no tokens with any LinkedIn name already held\nfor that `provider_id` (a verified prospect or a post engager), the\nwhole batch is rejected with ModelRetry before anything queues —\nfix the pairing and re-send (use the person's LinkedIn\nURL/identifier if the pid is wrong; use one consistent name if it's\nthe same person). A pid with no held name isn't gated here — it\nresolves before send and the resolver adjudicates the name then."New value: +"List of recipient dicts. Each dict must carry one of\n`linkedin_slug` (a profile slug or URL), `provider_id`, or `chat_id`,\ncopied verbatim from the tool result that surfaced the person — never a\nslug rebuilt from a display name, which enrolls the wrong person.\nOther attributes go under `data`; unknown top-level keys are\nrejected. If you have no identifier for someone, omit them rather than\nguess. When a recipient's\n`target_name` shares no tokens with any LinkedIn name already held\nfor that `provider_id` (a verified prospect or a post engager), the\nwhole batch is rejected with ModelRetry before anything queues —\nfix the pairing and re-send (use the person's LinkedIn\nURL/identifier if the pid is wrong; use one consistent name if it's\nthe same person). A pid with no held name isn't gated here — it\nresolves before send and the resolver adjudicates the name then."
    • changedInput schema / properties / recipients / items / description
      Previous value: -"One LinkedIn outreach recipient for queue_linkedin_outreach. Every field is\noptional at the schema layer; the model validator enforces that each recipient\ncarries one of linkedin_url / provider_id / chat_id. Unknown keys are rejected\n(extra='forbid') — other attributes go under `data`."New value: +"One LinkedIn outreach recipient for queue_linkedin_outreach. Every field is\noptional at the schema layer; the model validator enforces that each recipient\ncarries one of linkedin_slug / provider_id / chat_id. Unknown keys are rejected\n(extra='forbid') — other attributes go under `data`."
    • addedInput schema / properties / recipients / items / properties / linkedin_slug
      Added value: +{
      +  "default": "",
      +  "description": "A LinkedIn username slug or profile URL, copied verbatim from the tool result that surfaced this person; the tool resolves the profile internally. Never rebuild it from a display name — a slugified name guess resolves to a different real person, enrolling a stranger in the sequence.",
      +  "type": "string"
      +}
    • removedInput schema / properties / recipients / items / properties / linkedin_url
      Removed value: -{
      -  "default": "",
      -  "description": "A LinkedIn profile URL or username slug, copied verbatim from the tool result that surfaced this person; the tool resolves the profile internally. Never rebuild it from a display name — a slugified name guess resolves to a different real person, enrolling a stranger in the sequence.",
      -  "type": "string"
      -}
    • changedInput schema / properties / recipients / items / properties / provider_id / description
      Previous value: -"A pre-resolved LinkedIn provider id, copied exactly as the tool that surfaced this person returned it. A username slug or profile URL goes in linkedin_url."New value: +"A pre-resolved LinkedIn provider id, copied exactly as the tool that surfaced this person returned it. A username slug or profile URL goes in linkedin_slug."
  3. Changed3 schema fields changed
    • removedInput schema / properties / identifiers
      Removed value: -{
      -  "description": "List of recipient dicts. Each dict must carry one of\n`linkedin_url` (a profile URL or slug), `provider_id`, or `chat_id`,\ncopied verbatim from the tool result that surfaced the person — never a\nslug rebuilt from a display name, which enrolls the wrong person.\nOther attributes go under `data`; unknown top-level keys are\nrejected. If you have no identifier for someone, omit them rather than\nguess. When a recipient's\n`target_name` shares no tokens with any LinkedIn name already held\nfor that `provider_id` (a verified prospect or a post engager), the\nwhole batch is rejected with ModelRetry before anything queues —\nfix the pairing and re-send (use the person's LinkedIn\nURL/identifier if the pid is wrong; use one consistent name if it's\nthe same person). A pid with no held name isn't gated here — it\nresolves before send and the resolver adjudicates the name then.",
      -  "items": {
      -    "additionalProperties": false,
      -    "description": "One LinkedIn outreach recipient for queue_linkedin_outreach. Every field is\noptional at the schema layer; the model validator enforces that each recipient\ncarries one of linkedin_url / provider_id / chat_id. Unknown keys are rejected\n(extra='forbid') — other attributes go under `data`.",
      -    "properties": {
      -      "chat_id": {
      -        "default": "",
      -        "description": "Only for action_type='message': a Unipile chat id from search_linkedin_message_history, to follow up on an existing conversation.",
      -        "type": "string"
      -      },
      -      "data": {
      -        "additionalProperties": true,
      -        "description": "Person attributes stored on the prospect's row.\nKeys beyond the named fields are stored as given.",
      -        "properties": {
      -          "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
      -          }
      -        },
      -        "title": "ProspectData",
      -        "type": "object"
      -      },
      -      "email": {
      -        "default": "",
      -        "description": "The recipient's work email, for cross-channel dedup onto one prospect row. Forward it when you have one from a prior find_email call or search_linkedin_people enrichment.",
      -        "type": "string"
      -      },
      -      "linkedin_url": {
      -        "default": "",
      -        "description": "A LinkedIn profile URL or username slug, copied verbatim from the tool result that surfaced this person; the tool resolves the profile internally. Never rebuild it from a display name — a slugified name guess resolves to a different real person, enrolling a stranger in the sequence.",
      -        "type": "string"
      -      },
      -      "message": {
      -        "default": "",
      -        "description": "The final personalized body text, already resolved to literal words. The tool substitutes nothing and rejects any merge-field or placeholder syntax, apart from a [[slot]] in double square brackets the user asked to fill in themselves while approving.",
      -        "type": "string"
      -      },
      -      "provider_id": {
      -        "default": "",
      -        "description": "A pre-resolved LinkedIn provider id, copied exactly as the tool that surfaced this person returned it. A username slug or profile URL goes in linkedin_url.",
      -        "type": "string"
      -      },
      -      "source": {
      -        "default": "",
      -        "description": "A provenance label stamped on the tracking row (e.g. the list or search the recipient came from).",
      -        "type": "string"
      -      },
      -      "target_name": {
      -        "default": "",
      -        "description": "The recipient's display name.",
      -        "type": "string"
      -      }
      -    },
      -    "title": "LinkedInRecipient",
      -    "type": "object"
      -  },
      -  "type": "array"
      -}
    • addedInput schema / properties / recipients
      Added value: +{
      +  "description": "List of recipient dicts. Each dict must carry one of\n`linkedin_url` (a profile URL or slug), `provider_id`, or `chat_id`,\ncopied verbatim from the tool result that surfaced the person — never a\nslug rebuilt from a display name, which enrolls the wrong person.\nOther attributes go under `data`; unknown top-level keys are\nrejected. If you have no identifier for someone, omit them rather than\nguess. When a recipient's\n`target_name` shares no tokens with any LinkedIn name already held\nfor that `provider_id` (a verified prospect or a post engager), the\nwhole batch is rejected with ModelRetry before anything queues —\nfix the pairing and re-send (use the person's LinkedIn\nURL/identifier if the pid is wrong; use one consistent name if it's\nthe same person). A pid with no held name isn't gated here — it\nresolves before send and the resolver adjudicates the name then.",
      +  "items": {
      +    "additionalProperties": false,
      +    "description": "One LinkedIn outreach recipient for queue_linkedin_outreach. Every field is\noptional at the schema layer; the model validator enforces that each recipient\ncarries one of linkedin_url / provider_id / chat_id. Unknown keys are rejected\n(extra='forbid') — other attributes go under `data`.",
      +    "properties": {
      +      "chat_id": {
      +        "default": "",
      +        "description": "Only for action_type='message': a Unipile chat id from search_linkedin_message_history, to follow up on an existing conversation.",
      +        "type": "string"
      +      },
      +      "data": {
      +        "additionalProperties": true,
      +        "description": "Person attributes stored on the prospect's row.\nKeys beyond the named fields are stored as given.",
      +        "properties": {
      +          "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
      +          }
      +        },
      +        "title": "ProspectData",
      +        "type": "object"
      +      },
      +      "email": {
      +        "default": "",
      +        "description": "The recipient's work email, for cross-channel dedup onto one prospect row. Forward it when you have one from a prior find_email call or search_linkedin_people enrichment.",
      +        "type": "string"
      +      },
      +      "linkedin_url": {
      +        "default": "",
      +        "description": "A LinkedIn profile URL or username slug, copied verbatim from the tool result that surfaced this person; the tool resolves the profile internally. Never rebuild it from a display name — a slugified name guess resolves to a different real person, enrolling a stranger in the sequence.",
      +        "type": "string"
      +      },
      +      "message": {
      +        "default": "",
      +        "description": "The final personalized body text, already resolved to literal words. The tool substitutes nothing and rejects any merge-field or placeholder syntax, apart from a [[slot]] in double square brackets the user asked to fill in themselves while approving.",
      +        "type": "string"
      +      },
      +      "provider_id": {
      +        "default": "",
      +        "description": "A pre-resolved LinkedIn provider id, copied exactly as the tool that surfaced this person returned it. A username slug or profile URL goes in linkedin_url.",
      +        "type": "string"
      +      },
      +      "source": {
      +        "default": "",
      +        "description": "A provenance label stamped on the tracking row (e.g. the list or search the recipient came from).",
      +        "type": "string"
      +      },
      +      "target_name": {
      +        "default": "",
      +        "description": "The recipient's display name.",
      +        "type": "string"
      +      }
      +    },
      +    "title": "LinkedInRecipient",
      +    "type": "object"
      +  },
      +  "type": "array"
      +}
    • changedInput schema / required
      Previous value: -[
      -  "identifiers",
      -  "action_type"
      -]New value: +[
      +  "recipients",
      +  "action_type"
      +]
  4. Changed1 schema field changed
    • changedInput schema / properties / identifiers / items / properties / provider_id / description
      Previous value: -"A pre-resolved LinkedIn provider id, from search_linkedin_people, search_linkedin_message_history, or query_prospects, copied exactly as that tool returned it. A username slug or profile URL goes in linkedin_url."New value: +"A pre-resolved LinkedIn provider id, copied exactly as the tool that surfaced this person returned it. A username slug or profile URL goes in linkedin_url."
  5. Changed5 schema fields changed
    • changedInput schema / properties / identifiers / description
      Previous value: -"List of recipient dicts. Each dict must carry one of\n`linkedin_url` (a profile URL or slug), `provider_id`, or `chat_id`,\ncopied verbatim from the tool result that surfaced the person — never a\nslug rebuilt from a display name, which enrolls the wrong person.\nFreeform attributes go under `data`; unknown top-level keys are\nrejected. If you have no identifier for someone, omit them rather than\nguess. When a recipient's\n`target_name` shares no tokens with any LinkedIn name already held\nfor that `provider_id` (a verified prospect or a post engager), the\nwhole batch is rejected with ModelRetry before anything queues —\nfix the pairing and re-send (use the person's LinkedIn\nURL/identifier if the pid is wrong; use one consistent name if it's\nthe same person). A pid with no held name isn't gated here — it\nresolves before send and the resolver adjudicates the name then."New value: +"List of recipient dicts. Each dict must carry one of\n`linkedin_url` (a profile URL or slug), `provider_id`, or `chat_id`,\ncopied verbatim from the tool result that surfaced the person — never a\nslug rebuilt from a display name, which enrolls the wrong person.\nOther attributes go under `data`; unknown top-level keys are\nrejected. If you have no identifier for someone, omit them rather than\nguess. When a recipient's\n`target_name` shares no tokens with any LinkedIn name already held\nfor that `provider_id` (a verified prospect or a post engager), the\nwhole batch is rejected with ModelRetry before anything queues —\nfix the pairing and re-send (use the person's LinkedIn\nURL/identifier if the pid is wrong; use one consistent name if it's\nthe same person). A pid with no held name isn't gated here — it\nresolves before send and the resolver adjudicates the name then."
    • changedInput schema / properties / identifiers / items / description
      Previous value: -"One LinkedIn outreach recipient for queue_linkedin_outreach. Every field is\noptional at the schema layer; the model validator enforces that each recipient\ncarries one of linkedin_url / provider_id / chat_id. Unknown keys are rejected\n(extra='forbid') — freeform attributes go under `data`."New value: +"One LinkedIn outreach recipient for queue_linkedin_outreach. Every field is\noptional at the schema layer; the model validator enforces that each recipient\ncarries one of linkedin_url / provider_id / chat_id. Unknown keys are rejected\n(extra='forbid') — other attributes go under `data`."
    • changedInput schema / properties / identifiers / items / properties / data / description
      Previous value: -"Freeform recipient attributes stored on the prospect row's data blob."New value: +"Person attributes stored on the prospect's row.\nKeys beyond the named fields are stored as given."
    • addedInput schema / properties / identifiers / 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 / identifiers / items / properties / data / title
      Added value: +"ProspectData"
  6. Added

TDQS

A4.5/5.0
Behavior5/5

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

Annotations only mark it non-read-only and destructive; the description adds substantial depth: automatic rate limiting, staggering, business-hours pacing, tracking-row creation, approval-hold behavior, and explicit refusal cases (e.g., no node_id on a flow-less agent that has never sent). It describes exactly what gets queued versus held versus skipped, well beyond the annotation hints.

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

Conciseness3/5

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

Structure is good (tagged summary and returns, front-loaded key rules), but the prose is long and dense, with the batching point restated at length ('27 prospects... ONE call... not 27 calls'). Much of the weight is justified because there is no output schema, yet it is more verbose than strictly needed.

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?

Given high complexity (8 params, batch mutation, approvals, rate limits) and no output schema, the description carries its full weight by documenting return fields like known_queued, awaiting_approval, tracking_skipped, skipped, and rate_limit_estimate. Nothing an agent needs to call it correctly appears to be missing.

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

Parameters3/5

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

Schema description coverage is 100%, so the input schema already documents recipients, node_id, agent_id, and the rest in detail. The description reinforces batching semantics and node_id conditions but does not add much parameter-level meaning not already in the schema, so the baseline 3 holds.

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 (queue connection requests, messages, InMails, follows, or a resolve lookup), names itself as 'the single LinkedIn outreach tool,' and distinguishes it from siblings by routing everything through linkedin_invite_queue. An agent can tell it apart from queue_email_outreach and manage_linkedin_invite_queue without opening a 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 rules: campaigns already sending without a flow and one-off sends call it without node_id, while sequenced campaigns must route through define_sequence and call with node_id. It also states the hard precondition 'Only call this AFTER the user has explicitly confirmed the outreach' and names the alternative tools (track_prospects, define_sequence).

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