CLI and MCP server for LinkedHelper automation.
This project is brought to you by Alexey Pelykh.
lhremote lets AI assistants (Claude, etc.) control LinkedHelper through the Model Context Protocol. It can:
npm install -g lhremote # or: npx lhremote --help
lhremote launch-app # start LinkedHelper with remote debugging
lhremote list-accounts # find your LinkedIn account ID
lhremote start-instance # start an instance (auto-selects single account)
lhremote campaign-create --file my-campaign.yaml # create a campaign
lhremote import-people-from-urls <campaignId> --urls "https://www.linkedin.com/in/..."
lhremote campaign-start <campaignId> --person-ids <id1,id2,...>
lhremote campaign-status <campaignId> # monitor progressPacing: LinkedIn monitors automated activity. See the Rate Limiting guide for recommended settings.
npm install -g lhremoteOr run directly with npx:
npx lhremote --helpAdd to your Claude Desktop configuration
(claude_desktop_config.json):
{
"mcpServers": {
"lhremote": {
"command": "npx",
"args": ["lhremote", "mcp"]
}
}
}Once configured, Claude can use every registered tool directly. A typical workflow:
find-app — Detect a running
LinkedHelper instance (or launch-app to
start one)list-accounts — See available LinkedIn
accountsstart-instance — Start an instance for
an accountdescribe-actions — Explore available
action typescampaign-create — Create a campaign
from YAML/JSON configurationimport-people-from-urls — Import
target LinkedIn profiles into the campaigncampaign-start — Run the campaigncampaign-status /
campaign-statistics — Monitor
progressquery-messages /
check-replies — Review messaging
resultsThe lhremote command provides the same functionality as
the MCP server. Every MCP tool has a corresponding CLI command except
where its reference section says otherwise.
lhremote find-app [--json]
lhremote launch-app
lhremote quit-applhremote list-accounts [--all-workspaces] [--cdp-port <port>] [--json]
lhremote list-workspaces [--cdp-port <port>] [--json]
lhremote start-instance <accountId> [--cdp-port <port>]
lhremote stop-instance <accountId> [--cdp-port <port>]
lhremote check-status [--cdp-port <port>] [--json]lhremote campaign-list [--include-archived] [--json]
lhremote campaign-create --file <path> | --yaml <config> | --json-input <config> [--cdp-port <port>] [--json]
lhremote campaign-get <campaignId> [--cdp-port <port>] [--json]
lhremote campaign-export <campaignId> [--format yaml|json] [--output <path>] [--cdp-port <port>]
lhremote campaign-update <campaignId> [--name <name>] [--description <text>] [--clear-description] [--cdp-port <port>] [--json]
lhremote campaign-delete <campaignId> [--cdp-port <port>] [--json]
lhremote campaign-erase <campaignId> [--cdp-port <port>] [--json]
lhremote campaign-start <campaignId> --person-ids <ids> | --person-ids-file <path> [--cdp-port <port>] [--json]
lhremote campaign-stop <campaignId> [--cdp-port <port>] [--json]lhremote campaign-status <campaignId> [--include-results] [--limit <n>] [--cdp-port <port>] [--json]
lhremote campaign-statistics <campaignId> [--action-id <id>] [--max-errors <n>] [--cdp-port <port>] [--json]
lhremote campaign-retry <campaignId> --person-ids <ids> | --person-ids-file <path> [--cdp-port <port>] [--json]lhremote campaign-add-action <campaignId> --name <name> --action-type <type> [--description <text>] [--cool-down <ms>] [--max-results <n>] [--action-settings <json>] [--cdp-port <port>] [--json]
lhremote campaign-remove-action <campaignId> <actionId> [--cdp-port <port>] [--json]
lhremote campaign-update-action <campaignId> <actionId> [--name <name>] [--description <text>] [--clear-description] [--cool-down <ms>] [--max-results <n>] [--action-settings <json>] [--cdp-port <port>] [--json]
lhremote campaign-reorder-actions <campaignId> --action-ids <ids> [--cdp-port <port>] [--json]
lhremote campaign-move-next <campaignId> <actionId> --person-ids <ids> | --person-ids-file <path> [--cdp-port <port>] [--json]lhremote campaign-exclude-list <campaignId> [--action-id <id>] [--cdp-port <port>] [--json]
lhremote campaign-exclude-add <campaignId> --person-ids <ids> | --person-ids-file <path> [--action-id <id>] [--cdp-port <port>] [--json]
lhremote campaign-exclude-remove <campaignId> --person-ids <ids> | --person-ids-file <path> [--action-id <id>] [--cdp-port <port>] [--json]
lhremote campaign-list-people <campaignId> [--action-id <id>] [--status <status>] [--limit <n>] [--offset <n>] [--cdp-port <port>] [--json]
lhremote campaign-remove-people <campaignId> --person-ids <ids> | --person-ids-file <path> [--cdp-port <port>] [--json]
lhremote import-people-from-urls <campaignId> --urls <urls> | --urls-file <path> [--cdp-port <port>] [--json]
lhremote collect-people <campaignId> <sourceUrl> [--limit <n>] [--max-pages <n>] [--page-size <n>] [--source-type <type>] [--cdp-port <port>] [--json]lhremote list-collections [--json]
lhremote create-collection <name> [--cdp-port <port>] [--json]
lhremote delete-collection <collectionId> [--cdp-port <port>] [--json]
lhremote add-people-to-collection <collectionId> --person-ids <ids> | --person-ids-file <path> [--cdp-port <port>] [--json]
lhremote remove-people-from-collection <collectionId> --person-ids <ids> | --person-ids-file <path> [--cdp-port <port>] [--json]
lhremote import-people-from-collection <collectionId> <campaignId> [--cdp-port <port>] [--json]lhremote query-profile --person-id <id> | --public-id <slug> [--include-positions] [--json]
lhremote query-profiles [--query <text>] [--company <name>] [--include-history] [--limit <n>] [--offset <n>] [--json]
lhremote query-profiles-bulk --person-id <id>... | --public-id <slug>... [--include-positions] [--json]
lhremote query-messages [--person-id <id>] [--chat-id <id>] [--search <text>] [--limit <n>] [--offset <n>] [--json]
lhremote check-replies [--since <timestamp>] [--cdp-port <port>] [--json]
lhremote scrape-messaging-history --person-ids <ids> | --person-ids-file <path> [--cdp-port <port>] [--json]lhremote visit-profile --person-id <id> | --url <url> [--extract-current-organizations] [--cdp-port <port>] [--json]
lhremote endorse-skills --person-id <id> | --url <url> [--skill-name <name>]... [--limit <n>] [--skip-if-not-endorsable] [--keep-campaign] [--cdp-port <port>] [--json]
lhremote enrich-profile --person-id <id> | --url <url> [--enrich-profile-info] [--enrich-phones] [--enrich-emails] [--enrich-socials] [--enrich-companies] [--keep-campaign] [--cdp-port <port>] [--json]
lhremote follow-person --person-id <id> | --url <url> [--mode <follow|unfollow>] [--skip-if-unfollowable] [--keep-campaign] [--cdp-port <port>] [--json]
lhremote unfollow-profile <profileUrl> [--dry-run] [--cdp-port <port>] [--json]
lhremote like-person-posts --person-id <id> | --url <url> [--number-of-articles <n>] [--number-of-posts <n>] [--max-age-of-articles <days>] [--max-age-of-posts <days>] [--should-add-comment] [--message-template <json>] [--skip-if-not-liked] [--keep-campaign] [--cdp-port <port>] [--json]
lhremote message-person --person-id <id> | --url <url> --message-template <json> [--subject-template <json>] [--reject-if-replied] [--reject-if-messaged] [--keep-campaign] [--cdp-port <port>] [--json]
lhremote send-invite --person-id <id> | --url <url> [--message-template <json>] [--save-as-lead-sn] [--keep-campaign] [--cdp-port <port>] [--json]
lhremote send-inmail --person-id <id> | --url <url> --message-template <json> [--subject-template <json>] [--reject-if-replied] [--proceed-on-out-of-credits] [--keep-campaign] [--cdp-port <port>] [--json]
lhremote remove-connection --person-id <id> | --url <url> [--keep-campaign] [--cdp-port <port>] [--json]lhremote get-feed [--count <n>] [--cursor <token>] [--cdp-port <port>] [--json]
lhremote get-post <postUrl> [--comment-count <n>] [--cdp-port <port>] [--json]
lhremote get-post-stats <postUrl> [--cdp-port <port>] [--json]
lhremote get-profile-activity <profile> [--count <n>] [--cursor <token>] [--cdp-port <port>] [--json]
lhremote search-posts <query> [--count <n>] [--cursor <n>] [--cdp-port <port>] [--json]
lhremote comment-on-post --url <url> --text <text> [--cdp-port <port>] [--json]
lhremote react-to-post <postUrl> [--type <like|celebrate|support|love|insightful|funny>] [--cdp-port <port>] [--json]
lhremote react-to-comment <postUrl> <commentUrn> [--type <like|celebrate|support|love|insightful|funny>] [--dry-run] [--cdp-port <port>] [--json]
lhremote dismiss-feed-post <feedIndex> [--dry-run] [--cdp-port <port>] [--json]
lhremote hide-feed-author <feedIndex> [--dry-run] [--cdp-port <port>] [--json]
lhremote hide-feed-author-profile <profileUrl> [--dry-run] [--cdp-port <port>] [--json]
lhremote unfollow-from-feed <feedIndex> [--dry-run] [--cdp-port <port>] [--json]lhremote build-url <sourceType> [--keywords <keywords>] [--current-company <id>]... [--past-company <id>]... [--geo <id>]... [--industry <id>]... [--school <id>]... [--network <code>]... [--profile-language <code>]... [--service-category <id>]... [--filter <spec>]... [--slug <slug>] [--id <id>] [--json]
lhremote resolve-entity <entityType> <query> [--limit <n>] [--json]
lhremote list-reference-data <dataType> [--json]lhremote describe-actions [--category <category>] [--type <type>] [--json]
lhremote get-errors [--cdp-port <port>] [--json]
lhremote dismiss-errors [--cdp-port <port>] [--json]
lhremote get-action-budget [--cdp-port <port>] [--json]
lhremote get-throttle-status [--cdp-port <port>] [--json]Most tools and CLI commands connect to LinkedHelper via the Chrome
DevTools Protocol (CDP). In addition to the tool-specific parameters
listed below, CDP-connected tools accept the following, except where a
tool's own reference section says otherwise. The Default
column describes the MCP tools; each CLI command exposes its own subset
of these flags, and the notes below give the differences that
matter:
| Parameter | CLI Flag | Type | Default | Description |
|---|---|---|---|---|
cdpPort |
--cdp-port |
number | auto-discovered | CDP debugging port |
cdpHost |
--cdp-host |
string | 127.0.0.1 |
CDP host address |
allowRemote |
--allow-remote |
boolean | false | Allow connections to non-loopback addresses |
accountId |
varies — see below | number | auto-select if single account (MCP) | LinkedHelper account to act as |
cdpPort is discovered, not defaulted.
Omit it and lhremote finds the port of the running LinkedHelper itself,
whatever that port happens to be; it raises
LinkedHelperNotRunningError or
LinkedHelperUnreachableError when no instance is reachable.
Pass a port only to target one specific instance — you do not need one
to reach a LinkedHelper that is not on 9222. Two exceptions:
quit-app takes cdpPort alone (no
cdpHost, allowRemote or
accountId) and does default to 9222; and a port is required
whenever cdpHost is non-loopback, because discovery scans
local processes only.
accountId is required once more than one account
is configured. With a single account the MCP tools resolve it
themselves; with several they cannot, and the call fails — most raise
AccountResolutionError, while start-instance
and stop-instance answer with their own message. Either
way, list-accounts gives you the ID. Every tool that
connects to a running LinkedHelper accepts accountId; the
ones that do not are the app-management tools (find-app,
launch-app, quit-app), which act on the
application rather than on an account, the reference tools
(build-linkedin-url, resolve-linkedin-entity,
list-linkedin-reference-data,
describe-actions), which open no connection, and the
profile-cache readers (query-profile,
query-profiles, query-profiles-bulk), which
search every account's local database and cannot be scoped to one. The
CLI never auto-resolves, at any account count:
start-instance / stop-instance take a
required positional <accountId>,
comment-on-post / react-to-post take
--account-id, and no other command exposes it.
Security warning: Enabling
allowRemotepermits CDP connections to remote hosts. CDP is an unsandboxed protocol that grants full control over the target browser — equivalent to remote code execution. Only enable this when the network path between your machine and the target host is fully secured (e.g., SSH tunnel, VPN, or trusted LAN).
find-appDetect running LinkedHelper application instances and their CDP connection details.
No parameters.
launch-appLaunch the LinkedHelper application with remote debugging enabled.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
cdpPort |
number | No | auto-select | CDP port to use |
force |
boolean | No | false | Kill existing LinkedHelper processes before launching |
quit-appQuit the LinkedHelper application.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
cdpPort |
number | No | 9222 | CDP port |
list-accountsList available LinkedHelper accounts. Returns account ID, LinkedIn
ID, name, and email for each account. Returns accounts in the currently
selected workspace by default (LinkedHelper 2.113.x+); set
includeAllWorkspaces to enumerate every workspace the
current LH user belongs to.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
includeAllWorkspaces |
boolean | No | false | When true, enumerates accounts across every workspace the user belongs to, not just the selected workspace |
cdpPort |
number | No | auto-discovered | CDP port |
list-workspacesList LinkedHelper workspaces the current LH user belongs to. Each
workspace includes the user's role and a selected flag
indicating the currently active workspace. Returns an empty list on
LinkedHelper versions that predate workspaces (pre-2.113.x).
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
cdpPort |
number | No | auto-discovered | CDP port |
start-instanceStart a LinkedHelper instance for a LinkedIn account.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
accountId |
number | No | auto-select if single account | Account ID |
cdpPort |
number | No | auto-discovered | CDP port |
stop-instanceStop a running LinkedHelper instance.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
accountId |
number | No | auto-select if single account | Account ID |
cdpPort |
number | No | auto-discovered | CDP port |
check-statusCheck LinkedHelper connection status, running instances, and database health.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
cdpPort |
number | No | auto-discovered | CDP port |
campaign-listList existing campaigns with summary statistics.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
includeArchived |
boolean | No | false | Include archived campaigns |
cdpPort |
number | No | auto-discovered | CDP port |
campaign-createCreate a new campaign from YAML or JSON configuration.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
config |
string | Yes | — | Campaign configuration in YAML or JSON format |
format |
string | No | yaml | Configuration format (yaml or json) |
cdpPort |
number | No | auto-discovered | CDP port |
campaign-getGet detailed campaign information including action chain.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
campaignId |
number | Yes | — | Campaign ID |
cdpPort |
number | No | auto-discovered | CDP port |
campaign-exportExport campaign configuration as YAML or JSON.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
campaignId |
number | Yes | — | Campaign ID |
format |
string | No | yaml | Export format (yaml or json) |
cdpPort |
number | No | auto-discovered | CDP port |
campaign-updateUpdate a campaign's name and/or description.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
campaignId |
number | Yes | — | Campaign ID |
name |
string | No | — | New campaign name |
description |
string | No | — | New description (empty string to clear) |
cdpPort |
number | No | auto-discovered | CDP port |
campaign-deleteDelete (archive) a campaign.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
campaignId |
number | Yes | — | Campaign ID |
cdpPort |
number | No | auto-discovered | CDP port |
campaign-erasePermanently erase a campaign and all related data from the database.
This is irreversible — unlike campaign-delete (which
archives), this removes all campaign data permanently.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
campaignId |
number | Yes | — | Campaign ID |
cdpPort |
number | No | auto-discovered | CDP port |
campaign-startStart a campaign with specified target persons. Returns immediately (async execution).
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
campaignId |
number | Yes | — | Campaign ID |
personIds |
number[] | Yes | — | Person IDs to target |
cdpPort |
number | No | auto-discovered | CDP port |
campaign-stopStop a running campaign.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
campaignId |
number | Yes | — | Campaign ID |
cdpPort |
number | No | auto-discovered | CDP port |
campaign-statusCheck campaign execution status and results.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
campaignId |
number | Yes | — | Campaign ID |
includeResults |
boolean | No | false | Include execution results |
limit |
number | No | 20 | Max results to return |
cdpPort |
number | No | auto-discovered | CDP port |
campaign-statisticsGet per-action statistics for a campaign.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
campaignId |
number | Yes | — | Campaign ID |
actionId |
number | No | — | Filter to a specific action |
maxErrors |
number | No | 5 | Max top errors per action |
cdpPort |
number | No | auto-discovered | CDP port |
campaign-retryReset specified people for re-run in a campaign.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
campaignId |
number | Yes | — | Campaign ID |
personIds |
number[] | Yes | — | Person IDs to retry |
cdpPort |
number | No | auto-discovered | CDP port |
campaign-add-actionAdd a new action to a campaign's action chain. Use
describe-actions to explore available action types.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
campaignId |
number | Yes | — | Campaign ID |
name |
string | Yes | — | Display name for the action |
actionType |
string | Yes | — | Action type (e.g., VisitAndExtract,
MessageToPerson) |
description |
string | No | — | Action description |
coolDown |
number | No | — | Milliseconds between executions |
maxResults |
number | No | — | Max results per iteration (-1 for unlimited) |
actionSettings |
object | No | — | Action-specific settings |
cdpPort |
number | No | auto-discovered | CDP port |
campaign-remove-actionRemove an action from a campaign's action chain.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
campaignId |
number | Yes | — | Campaign ID |
actionId |
number | Yes | — | Action ID to remove |
cdpPort |
number | No | auto-discovered | CDP port |
campaign-update-actionUpdate an existing action's configuration in a campaign. Only provided fields are changed.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
campaignId |
number | Yes | — | Campaign ID |
actionId |
number | Yes | — | Action ID to update |
name |
string | No | — | New display name |
description |
string | null | No | — | New description (null to clear) |
coolDown |
number | No | — | Milliseconds between executions |
maxActionResultsPerIteration |
number | No | — | Max results per iteration (-1 for unlimited) |
actionSettings |
string | No | — | Action-specific settings as JSON (merged with existing) |
cdpPort |
number | No | auto-discovered | CDP port |
campaign-reorder-actionsReorder actions in a campaign's action chain.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
campaignId |
number | Yes | — | Campaign ID |
actionIds |
number[] | Yes | — | Action IDs in desired order |
cdpPort |
number | No | auto-discovered | CDP port |
campaign-move-nextMove people from one action to the next in a campaign.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
campaignId |
number | Yes | — | Campaign ID |
actionId |
number | Yes | — | Action ID to move people from |
personIds |
number[] | Yes | — | Person IDs to move |
cdpPort |
number | No | auto-discovered | CDP port |
campaign-exclude-listView the exclude list for a campaign or action.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
campaignId |
number | Yes | — | Campaign ID |
actionId |
number | No | — | Action ID (for action-level list) |
cdpPort |
number | No | auto-discovered | CDP port |
campaign-exclude-addAdd people to a campaign or action exclude list.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
campaignId |
number | Yes | — | Campaign ID |
personIds |
number[] | Yes | — | Person IDs to exclude |
actionId |
number | No | — | Action ID (for action-level list) |
cdpPort |
number | No | auto-discovered | CDP port |
campaign-exclude-removeRemove people from a campaign or action exclude list.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
campaignId |
number | Yes | — | Campaign ID |
personIds |
number[] | Yes | — | Person IDs to remove from exclude list |
actionId |
number | No | — | Action ID (for action-level list) |
cdpPort |
number | No | auto-discovered | CDP port |
campaign-list-peopleList people assigned to a campaign with their processing status.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
campaignId |
number | Yes | — | Campaign ID |
actionId |
number | No | — | Filter to a specific action |
status |
string | No | — | Filter by status (queued, processed,
successful, failed) |
limit |
number | No | 20 | Max results |
offset |
number | No | 0 | Pagination offset |
cdpPort |
number | No | auto-discovered | CDP port |
campaign-remove-peopleRemove people from a campaign's target list entirely. This is the
inverse of import-people-from-urls.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
campaignId |
number | Yes | — | Campaign ID |
personIds |
number[] | Yes | — | Person IDs to remove |
cdpPort |
number | No | auto-discovered | CDP port |
import-people-from-urlsImport LinkedIn profile URLs into a campaign action target list. Idempotent — previously imported URLs are skipped.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
campaignId |
number | Yes | — | Campaign ID |
urls |
string[] | Yes | — | LinkedIn profile URLs |
cdpPort |
number | No | auto-discovered | CDP port |
collect-peopleCollect people from a LinkedIn page into a campaign. Detects the source type from the URL automatically.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
campaignId |
number | Yes | — | Campaign ID to collect into |
sourceUrl |
string | Yes | — | LinkedIn page URL (search results, company people, group members) |
limit |
number | No | — | Max profiles to collect |
maxPages |
number | No | — | Max pages to process |
pageSize |
number | No | — | Results per page |
sourceType |
string | No | — | Explicit source type (bypasses URL detection) |
cdpPort |
number | No | auto-discovered | CDP port |
list-collectionsList all LinkedHelper collections (Lists) with people counts.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
cdpPort |
number | No | auto-discovered | CDP port |
create-collectionCreate a new named LinkedHelper collection (List).
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
name |
string | Yes | — | Name for the new collection |
cdpPort |
number | No | auto-discovered | CDP port |
delete-collectionDelete a LinkedHelper collection (List) and all its people associations.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
collectionId |
number | Yes | — | Collection ID to delete |
cdpPort |
number | No | auto-discovered | CDP port |
add-people-to-collectionAdd people to a LinkedHelper collection. Idempotent — adding an already-present person is a no-op.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
collectionId |
number | Yes | — | Collection ID |
personIds |
number[] | Yes | — | Person IDs to add |
cdpPort |
number | No | auto-discovered | CDP port |
remove-people-from-collectionRemove people from a LinkedHelper collection.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
collectionId |
number | Yes | — | Collection ID |
personIds |
number[] | Yes | — | Person IDs to remove |
cdpPort |
number | No | auto-discovered | CDP port |
import-people-from-collectionImport all people from a LinkedHelper collection into a campaign. Large sets are automatically chunked.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
collectionId |
number | Yes | — | Collection ID to import from |
campaignId |
number | Yes | — | Campaign ID to import into |
cdpPort |
number | No | auto-discovered | CDP port |
query-profileLook up a cached LinkedIn profile from the local database by person ID or public ID.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
personId |
number | No | — | Internal person ID |
publicId |
string | No | — | LinkedIn public ID (URL slug) |
includePositions |
boolean | No | false | Include full position history (career history) |
query-profilesSearch for profiles in the local database with name, headline, or company filters.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
query |
string | No | — | Search name or headline |
company |
string | No | — | Filter by company |
includeHistory |
boolean | No | false | Also search past positions (company history), not just current |
limit |
number | No | 20 | Max results |
offset |
number | No | 0 | Pagination offset |
query-profiles-bulkLook up multiple cached LinkedIn profiles in a single call.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
personIds |
number[] | No | — | Look up by internal person IDs |
publicIds |
string[] | No | — | Look up by LinkedIn public IDs (URL slugs) |
includePositions |
boolean | No | false | Include full position history |
query-messagesQuery messaging history from the local database.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
personId |
number | No | — | Filter by person ID |
chatId |
number | No | — | Show specific conversation thread |
search |
string | No | — | Search message text |
limit |
number | No | 20 | Max results |
offset |
number | No | 0 | Pagination offset |
check-repliesCheck for new message replies from LinkedIn.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
since |
string | No | — | Only show replies after this ISO timestamp |
cdpPort |
number | No | auto-discovered | CDP port |
scrape-messaging-historyScrape messaging history from LinkedIn for specified people into the local database. This is a long-running operation that may take several minutes.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
personIds |
number[] | Yes | — | Person IDs whose messaging history should be scraped |
cdpPort |
number | No | auto-discovered | CDP port |
visit-profileVisit a LinkedIn profile via LinkedHelper's VisitAndExtract action and return the extracted profile data. Deducts from the daily action budget.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
personId |
number | No | — | Internal person ID (provide this or url) |
url |
string | No | — | LinkedIn profile URL (provide this or personId) |
extractCurrentOrganizations |
boolean | No | false | Extract current company info during visit |
cdpPort |
number | No | auto-discovered | CDP port |
endorse-skillsEndorse skills on a LinkedIn profile via an ephemeral campaign. Deducts from the daily action budget.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
personId |
number | No | — | Internal person ID (provide this or url) |
url |
string | No | — | LinkedIn profile URL (provide this or personId) |
skillNames |
string[] | No | — | Specific skill names to endorse (mutually exclusive with
limit) |
limit |
number | No | — | Max number of skills to endorse (mutually exclusive with
skillNames) |
skipIfNotEndorsable |
boolean | No | true | Skip if person has no endorsable skills |
keepCampaign |
boolean | No | false | Archive the ephemeral campaign instead of deleting it |
cdpPort |
number | No | auto-discovered | CDP port |
enrich-profileEnrich a LinkedIn profile by extracting additional data (emails, phones, socials, company info) via an ephemeral campaign. Deducts from the daily action budget.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
personId |
number | No | — | Internal person ID (provide this or url) |
url |
string | No | — | LinkedIn profile URL (provide this or personId) |
profileInfo |
object | No | — | Enrich profile info (shouldEnrich required) |
phones |
object | No | — | Enrich phone numbers (shouldEnrich required) |
emails |
object | No | — | Enrich email addresses (shouldEnrich required) |
socials |
object | No | — | Enrich social profiles (shouldEnrich required) |
companies |
object | No | — | Enrich company data (shouldEnrich required) |
keepCampaign |
boolean | No | false | Archive the ephemeral campaign instead of deleting it |
cdpPort |
number | No | auto-discovered | CDP port |
follow-personFollow or unfollow a LinkedIn profile via an ephemeral campaign. Deducts from the daily action budget.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
personId |
number | No | — | Internal person ID (provide this or url) |
url |
string | No | — | LinkedIn profile URL (provide this or personId) |
mode |
string | No | follow |
follow or unfollow |
skipIfUnfollowable |
boolean | No | true | Skip if person cannot be unfollowed |
keepCampaign |
boolean | No | false | Archive the ephemeral campaign instead of deleting it |
cdpPort |
number | No | auto-discovered | CDP port |
unfollow-profileUnfollow a LinkedIn member profile or organization page by navigating
to it and clicking the Following → Unfollow toggle. Accepts both profile
URLs and company URLs — LinkedIn renders the same Follow/Following
toggle on both surfaces. Prefer this over
unfollow-from-feed for bulk feed-hygiene workflows:
feed-based tools are limited to one action per feed fetch because the
feed DOM refreshes after each hide/unfollow, invalidating other indexes;
this tool works regardless of whether the author is currently in the
home feed. For org-level feed-volume escalation it is also the
substitute for a mute, since LinkedIn does not expose a Mute action on
company pages. Returns the detected prior follow state and target kind
(profile vs company), so bulk workflows can distinguish actual unfollows
from no-op calls on already-unfollowed targets and from inaccessible
targets (private/blocked profiles, restricted/unavailable companies).
Unlike follow-person with mode: unfollow,
which runs an ephemeral campaign and deducts from the daily action
budget, this acts on the page directly and does not.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
profileUrl |
string | Yes | — | LinkedIn profile URL (e.g.
https://www.linkedin.com/in/{publicId}/) or company URL
(e.g. https://www.linkedin.com/company/{slug}/) |
dryRun |
boolean | No | false |
When true, detects the follow state but does not click Unfollow (the dialog is opened and dismissed) |
cdpPort |
number | No | auto-discovered | CDP port |
like-person-postsLike and optionally comment on posts and articles by a LinkedIn profile via an ephemeral campaign. Deducts from the daily action budget.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
personId |
number | No | — | Internal person ID (provide this or url) |
url |
string | No | — | LinkedIn profile URL (provide this or personId) |
numberOfArticles |
number | No | — | Number of articles to like |
numberOfPosts |
number | No | — | Number of posts to like |
maxAgeOfArticles |
number | No | — | Maximum age of articles in days |
maxAgeOfPosts |
number | No | — | Maximum age of posts in days |
shouldAddComment |
boolean | No | false | Also add a comment to liked posts/articles |
messageTemplate |
string | No | — | Comment text template as JSON (required when
shouldAddComment is true) |
skipIfNotLiked |
boolean | No | true | Skip if nothing was liked |
keepCampaign |
boolean | No | false | Archive the ephemeral campaign instead of deleting it |
cdpPort |
number | No | auto-discovered | CDP port |
message-personSend a direct message to a 1st-degree LinkedIn connection via an ephemeral campaign. Deducts from the daily action budget.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
personId |
number | No | — | Internal person ID (provide this or url) |
url |
string | No | — | LinkedIn profile URL (provide this or personId) |
messageTemplate |
string | Yes | — | Message template as JSON |
subjectTemplate |
string | No | — | Subject line template as JSON |
rejectIfReplied |
boolean | No | false | Skip if person already replied |
rejectIfMessaged |
boolean | No | false | Skip if already messaged |
keepCampaign |
boolean | No | false | Archive the ephemeral campaign instead of deleting it |
cdpPort |
number | No | auto-discovered | CDP port |
send-inviteSend a LinkedIn connection request via an ephemeral campaign. Deducts from the daily action budget.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
personId |
number | No | — | Internal person ID (provide this or url) |
url |
string | No | — | LinkedIn profile URL (provide this or personId) |
messageTemplate |
string | No | — | Invitation message template as JSON (empty for no message) |
saveAsLeadSN |
boolean | No | false | Save as lead in Sales Navigator |
keepCampaign |
boolean | No | false | Archive the ephemeral campaign instead of deleting it |
cdpPort |
number | No | auto-discovered | CDP port |
send-inmailSend an InMail message to a LinkedIn member (no connection required) via an ephemeral campaign. Deducts from the daily action budget.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
personId |
number | No | — | Internal person ID (provide this or url) |
url |
string | No | — | LinkedIn profile URL (provide this or personId) |
messageTemplate |
string | Yes | — | InMail body template as JSON |
subjectTemplate |
string | No | — | InMail subject line template as JSON |
rejectIfReplied |
boolean | No | false | Skip if person already replied |
proceedOnOutOfCredits |
boolean | No | false | Continue even when InMail credits are exhausted |
keepCampaign |
boolean | No | false | Archive the ephemeral campaign instead of deleting it |
cdpPort |
number | No | auto-discovered | CDP port |
remove-connectionRemove a person from 1st-degree LinkedIn connections (unfriend) via an ephemeral campaign. Deducts from the daily action budget.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
personId |
number | No | — | Internal person ID (provide this or url) |
url |
string | No | — | LinkedIn profile URL (provide this or personId) |
keepCampaign |
boolean | No | false | Archive the ephemeral campaign instead of deleting it |
cdpPort |
number | No | auto-discovered | CDP port |
get-feedRead the LinkedIn home feed. Returns structured post data with cursor-based pagination.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
count |
number | No | 10 | Number of posts per page |
cursor |
string | No | — | Cursor token from a previous call for the next page |
cdpPort |
number | No | auto-discovered | CDP port |
get-postGet detailed data for a single LinkedIn post including its comment thread.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
postUrl |
string | Yes | — | LinkedIn post URL or URN |
commentCount |
number | No | 100 | Maximum number of comments to load (0 to skip) |
cdpPort |
number | No | auto-discovered | CDP port |
get-post-statsGet engagement statistics for a LinkedIn post: reaction count (broken down by type), comment count, and share count.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
postUrl |
string | Yes | — | LinkedIn post URL or URN |
cdpPort |
number | No | auto-discovered | CDP port |
get-post-engagersList people who engaged with a LinkedIn post (reacted, etc.) with
their profile info and engagement type. Supports pagination. Returns a
shortfall field alongside the engagers: null
when no reaction count the page rendered contradicted what was collected
— the absence of a contradiction, not a guarantee of completeness —
otherwise a record of how many rows were collected, how many were asked
for, the reaction count the page itself rendered, and why collection
stopped. paging.total carries the page's own reaction count
where that was readable and falls back to the number of rows
collected where it was not — the pre-pagination count, not the
length of the returned slice, so with start > 0 it can
exceed the rows you got back. Either way it is not an independent answer
to whether more exist. MCP tool only — no CLI command.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
postUrl |
string | Yes | — | LinkedIn post URL or URN |
start |
number | No | 0 | Pagination offset |
count |
number | No | 20 | Number of engagers per page |
cdpPort |
number | No | auto-discovered | CDP port |
get-profile-activityGet recent posts/activity from a LinkedIn profile with cursor-based pagination.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
profile |
string | Yes | — | LinkedIn profile public ID or URL |
count |
number | No | 10 | Number of posts per page |
cursor |
string | No | — | Cursor token from a previous call for the next page |
cdpPort |
number | No | auto-discovered | CDP port |
search-postsSearch LinkedIn for posts by keyword or hashtag. Returns structured post data with cursor-based pagination.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
query |
string | Yes | — | Search query — keywords or hashtag |
count |
number | No | 10 | Number of results per page |
cursor |
number | No | — | Index-based cursor from a previous call for the next page |
cdpPort |
number | No | auto-discovered | CDP port |
comment-on-postPost a comment on a LinkedIn post. Checks action budget before attempting.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
postUrl |
string | Yes | — | LinkedIn post URL |
text |
string | Yes | — | Comment text to post |
cdpPort |
number | No | auto-discovered | CDP port |
react-to-postReact to a LinkedIn post with a specific reaction type. Checks action
budget before attempting — refused when the PostLike limit is reached,
including under dryRun.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
postUrl |
string | Yes | — | LinkedIn post URL |
reactionType |
string | No | like |
like, celebrate, support,
love, insightful, or funny |
dryRun |
boolean | No | false |
When true, detects current reaction state without clicking |
cdpPort |
number | No | auto-discovered | CDP port |
react-to-commentReact to a specific LinkedIn comment with a specific reaction type.
Use this for the organic-engagement pattern of acknowledging a reply
(e.g., the post author replied to your comment) without extending the
thread. Mirrors react-to-post semantics scoped to one
comment via its URN (as returned by get-post's
commentUrn field).
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
postUrl |
string | Yes | — | LinkedIn post URL containing the target comment |
commentUrn |
string | Yes | — | Comment URN (e.g.
urn:li:comment:(activity:1234567890,9876543210)) |
reactionType |
string | No | like |
like, celebrate, support,
love, insightful, or funny |
dryRun |
boolean | No | false |
When true, detects current reaction state without clicking |
cdpPort |
number | No | auto-discovered | CDP port |
dismiss-feed-postDismiss a post from the LinkedIn feed by clicking
Not interested in its three-dot menu. Operates on the home
feed by position index (pair with get-feed to identify
posts).
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
feedIndex |
number | Yes | — | Zero-based index of the post in the visible LinkedIn feed (pair with
get-feed to identify posts) |
dryRun |
boolean | No | false |
When true, locates the menu item but does not click it |
cdpPort |
number | No | auto-discovered | CDP port |
hide-feed-authorClick Hide posts by {Name} in a feed post's three-dot
menu. Operates on the home feed by position index (pair with
get-feed to identify posts). The hidden person may differ
from the original author (e.g., a reposter). For bulk feed-hygiene
workflows prefer hide-feed-author-profile, which is not
limited to one action per feed fetch.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
feedIndex |
number | Yes | — | Zero-based index of the post in the visible LinkedIn feed (pair with
get-feed to identify posts) |
dryRun |
boolean | No | false |
When true, locates the menu item but does not click it |
cdpPort |
number | No | auto-discovered | CDP port |
hide-feed-author-profileMute a LinkedIn profile's posts in the home feed by navigating to the
profile page and invoking Mute {Name} from the More menu.
Prefer this over hide-feed-author for bulk feed-hygiene
workflows: feed-based tools are limited to one action per feed fetch
because the feed DOM refreshes after each hide/unfollow, invalidating
other indexes. Works regardless of whether the author is currently in
the feed. Returns structured results: success when muted;
{ success: false, reason: "mute_not_available" } for
non-connections or profiles where Mute is not exposed;
{ success: false, reason: "already_muted" } when the
profile is already muted. LinkedIn exposes no Mute action on company
pages; use unfollow-profile for an organization.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
profileUrl |
string | Yes | — | LinkedIn profile URL (e.g.
https://www.linkedin.com/in/{publicId}/) |
dryRun |
boolean | No | false |
When true, opens the More menu and detects mute availability but does not click Mute |
cdpPort |
number | No | auto-discovered | CDP port |
unfollow-from-feedUnfollow the author of a LinkedIn feed post via its three-dot menu.
Operates on the home feed by position index (pair with
get-feed to identify posts). For bulk feed-hygiene
workflows prefer unfollow-profile, which is not limited to
one action per feed fetch.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
feedIndex |
number | Yes | — | Zero-based index of the post in the visible LinkedIn feed (pair with
get-feed to identify posts) |
dryRun |
boolean | No | false |
When true, locates the menu item but does not click it |
cdpPort |
number | No | auto-discovered | CDP port |
build-linkedin-urlBuild a LinkedIn URL for any supported source type. Supports
SearchPage (basic search with faceted filters), SNSearchPage (Sales
Navigator), and parameterised templates for company, school, group, and
event pages. CLI command: build-url.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
sourceType |
string | Yes | — | LinkedIn source type (e.g., SearchPage,
SNSearchPage, OrganizationPeople) |
keywords |
string | No | — | Search keywords |
currentCompany |
string[] | No | — | Current company IDs (SearchPage) |
pastCompany |
string[] | No | — | Past company IDs (SearchPage) |
geoUrn |
string[] | No | — | Geographic URN IDs (SearchPage) |
industry |
string[] | No | — | Industry IDs (SearchPage) |
school |
string[] | No | — | School IDs (SearchPage) |
network |
string[] | No | — | Connection degree codes: F, S,
O (SearchPage) |
profileLanguage |
string[] | No | — | Profile language codes (SearchPage) |
serviceCategory |
string[] | No | — | Service category IDs (SearchPage) |
filters |
object[] | No | — | Sales Navigator filters (SNSearchPage) — each with
type, values[] |
slug |
string | No | — | Company or school slug (OrganizationPeople, Alumni) |
id |
string | No | — | Entity ID (Group, Event, SNListPage, etc.) |
resolve-linkedin-entityResolve human-readable names (company names, locations, schools) to
LinkedIn entity IDs via LinkedIn's public typeahead endpoint. No
authentication and no running LinkedHelper instance required. CLI
command: resolve-entity.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
query |
string | Yes | — | Search query (e.g., company name, city) |
entityType |
string | Yes | — | COMPANY, GEO, or SCHOOL |
list-linkedin-reference-dataList LinkedIn reference data for finite enumerations (industries,
seniority levels, functions, company sizes, connection degrees, profile
languages). Use this to discover valid IDs for search filters. CLI
command: list-reference-data.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
dataType |
string | Yes | — | INDUSTRY, SENIORITY,
FUNCTION, COMPANY_SIZE,
CONNECTION_DEGREE, or PROFILE_LANGUAGE |
describe-actionsList available LinkedHelper action types with descriptions and configuration schemas.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
category |
string | No | — | Filter by category (people, messaging,
engagement, crm, workflow) |
actionType |
string | No | — | Get details for a specific action type |
get-errorsQuery current LinkedHelper UI errors, dialogs, and blocking popups.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
cdpPort |
number | No | auto-discovered | CDP port |
dismiss-errorsDismiss closable error popups in the LinkedHelper instance UI by
clicking their close/OK buttons. Recommended after
UIBlockedError.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
cdpPort |
number | No | auto-discovered | CDP port |
get-action-budgetGet daily action budget showing limit types, thresholds, and today's usage from LH campaigns and CDP-direct actions.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
cdpPort |
number | No | auto-discovered | CDP port |
get-throttle-statusCheck if LinkedIn is currently throttling the account.
| Parameter | Type | Required | Default | Description |
|---|---|---|---|---|
cdpPort |
number | No | auto-discovered | CDP port |
LINKEDHELPER_PATH environment
variable.query-profile
and query-profiles search the local LinkedHelper database.
Profiles must have been visited or imported by LinkedHelper to appear in
results.scrape-messaging-history navigates LinkedIn's messaging UI
and can take several minutes depending on conversation volume.Error:
LinkedHelper is not running (no processes found) — or
LinkedHelper is not running (no CDP endpoint at port <port>)
when you named a port explicitly.
Solution: Use launch-app to start
LinkedHelper, or start it manually. lhremote communicates with
LinkedHelper via the Chrome DevTools Protocol (CDP), which requires the
application to be running. The second form means nothing answered on the
port you named: drop --cdp-port and lhremote will discover
the running instance's port itself.
Error:
LinkedHelper processes detected but CDP endpoint is unreachable
Solution: LinkedHelper is running but its CDP port
is not responding. This typically means a stale or zombie process. Use
launch-app --force to kill stale processes and relaunch, or
manually restart LinkedHelper.
Error:
LinkedHelper application binary not found. Set LINKEDHELPER_PATH to override.
Solution: Install LinkedHelper from linkedhelper.com. If installed in a
non-standard location, set the LINKEDHELPER_PATH
environment variable to the binary path.
Error: No accounts found.
Solution: Open LinkedHelper and configure at least one LinkedIn account before using lhremote.
Error:
Multiple accounts found. Specify accountId. Use list-accounts to see available accounts.
Solution: Use list-accounts to see
available accounts, then pass the desired accountId to
start-instance, stop-instance, or other
tools.
Error:
No LinkedHelper instance is running. Use start-instance first.
Solution: Run start-instance before
using campaign or messaging tools. An instance must be running to
interact with LinkedIn.
Error:
Instance started but failed to initialize within timeout.
Solution: The instance was started but took too long
to finish loading. This can happen on slow connections. Try again; the
instance may still be starting in the background. Use
check-status to verify.
Error:
No database found for account
Solution: The LinkedHelper database file is missing for the specified account. Ensure the account has been used at least once in LinkedHelper so that a local database has been created.
lhremote is an independent project not
affiliated with, endorsed by, or officially connected to:
LinkedIn is a trademark of LinkedIn Corporation. LinkedHelper is a trademark of its respective owner.
This project enables interoperability between automation tools and LinkedHelper, as permitted under DMCA § 1201(f). Implementation is based on publicly observable behavior (Chrome DevTools Protocol) without access to protected source code.
Use of lhremote requires a valid LinkedHelper
subscription and is subject to LinkedHelper's and LinkedIn's terms of
service. Users accept all responsibility for compliance.
This tool is for legitimate productivity. Do NOT use for spam, scraping at scale, or harassment.
AGPL-3.0-only — For commercial licensing, contact the maintainer.