Skip to content
Scalekit Docs
Talk to an Engineer Dashboard

Connect AI agents to the Lemlist MCP server

Vendor MCP
Open markdown

The Lemlist MCP connector routes your AI agent's tool calls to Lemlist's own MCP server through Scalekit. Each user signs in to Lemlist once, and Scalekit stores and refreshes their tokens, so your agent never handles credentials. It comes with 123 tools.

Tools
123
What they doRead · write · destructive
59 · 34 · 3059 read34 write30 destructive
Users sign in with
OAuth app
Scalekit's or your own

Setup

  1. Install the SDK

    Terminal window
    npm install @scalekit-sdk/node dotenv
  2. Set your credentials

    Add your Scalekit credentials to your .env file. Find values in app.scalekit.com > Developers > API Credentials.

    .env
    SCALEKIT_ENVIRONMENT_URL=<your-environment-url>
    SCALEKIT_CLIENT_ID=<your-client-id>
    SCALEKIT_CLIENT_SECRET=<your-client-secret>
  3. Create the Lemlist MCP connection

    In AgentKit > Connections, create a Lemlist MCP connection. The name you give it is the connection_name your code passes. See Configure connections.

    Scalekit credentials are available for Lemlist MCP server, so you don't need to register an OAuth app.

  4. Authorize a user and make your first call

    quickstart.mts
    import { ScalekitClient } from '@scalekit-sdk/node'
    import 'dotenv/config'
    import { createInterface } from 'node:readline/promises'
    const scalekit = new ScalekitClient(
    process.env.SCALEKIT_ENVIRONMENT_URL,
    process.env.SCALEKIT_CLIENT_ID,
    process.env.SCALEKIT_CLIENT_SECRET,
    )
    const actions = scalekit.actions
    const connector = 'lemlistmcp'
    const identifier = 'user_123'
    // Generate an authorization link for the user
    const { link } = await actions.getAuthorizationLink({ connectionName: connector, identifier })
    console.log('Authorize Lemlist MCP:', link)
    const rl = createInterface({ input: process.stdin, output: process.stdout })
    await rl.question('Press Enter after authorizing...')
    rl.close()
    // Make your first call
    const result = await actions.executeTool({
    connector,
    identifier,
    toolName: 'lemlistmcp_bulk_get_enrichment_results',
    toolInput: {},
    })
    console.log(result)
    Terminal window
    npx tsx quickstart.mts

    Each user signs in once. See Authorize a user for the full flow and statuses.

Tools

Pass the exact name to execute_tool
Try in PlaygroundRequest a tool
  • lemlistmcp_bulk_get_enrichment_resultsPoll the results of one or more enrichment jobs.Read-only

    Bulk Get Enrichment Results

    Poll the results of one or more enrichment jobs. Provide a dataRef from bulk_enrich_data or a nextPollRef from a previous poll.

    Inputs

    dataRefstring
    dataRef from bulk_enrich_data (first poll) OR nextPollRef from a previous poll result (subsequent polls). Mutually exclusive with enrichmentIds.
    enrichmentIdsarray
    Array of enrichment IDs to poll (1-500, enr_xxx format). Mutually exclusive with dataRef.
  • lemlistmcp_calculate_infrastructureCompute cold-email infrastructure sizing from campaign inputs.Read-only

    Calculate Infrastructure

    Compute cold-email infrastructure sizing from campaign inputs. Returns: - Peak daily volume + injection rate - Mailbox count (raw and with safety buffer) - ESP split (Google / Microsoft / SMTP) - Domains needed per seat (cap: 3 mailboxes per domain) - Warmup timeline (rampup + 14-day stabilization) - Lemlist slot cost (included + extras at $9/month) - Brand-consistent domain name suggestions - Mailbox naming proposals per SDR (when sdrs provided), with SDR userId echoed back in `sdrPlans[].userId` if it was passed in All math is deterministic — pure domain function, no LLM math. Use in Step 2 of the outreach-infra skill, after gathering campaign inputs via ask_question.

    Inputs

    companyDomainstringrequired
    Team company domain
    delayDaysintegerrequired
    Days between sequence steps
    espChoicestringrequired
    ESP matching strategyone of googlemicrosoftgoogle+microsoftall-three
    leadsintegerrequired
    Total leads over the timeline
    rampupIncrementintegerrequired
    Lemwarm daily rampup incrementone of 12
    seatsintegerrequired
    SDR seats
    sequenceStepsintegerrequired
    Steps in the sequence
    timelineDaysintegerrequired
    Campaign duration in days
    domainSuggestionsCountinteger
    Domain suggestions to return (default 10)
    sdrsarray
    Optional SDR identities for mailbox naming. Each entry may include the SDR lemlist userId — when provided, it is echoed back in `sdrPlans[].userId` so the caller can wire `assignToUser` in provision_mailboxes without name matching.
  • lemlistmcp_check_domain_healthCheck DNS health for email sending domains (MX, SPF, DMARC, blacklists).Read-only

    Check Domain Health

    Check DNS health for email sending domains (MX, SPF, DMARC, blacklists). Returns score (0-100), per-check status, and DNS fix records.

    Inputs

    domainstring
    Domain to check (e.g. example.com). If omitted, checks domains from team members.
    mailboxIdstring
    Mailbox ID to check DNS for.
    scopestring
    Check scope: user (current user) or team (all members). Default: team.one of userteamdefault team
  • lemlistmcp_display_leadsShow leads in an interactive workspace table.Read-only

    Display Leads

    Show leads in an interactive workspace table. Users can select, filter, and push to campaigns. For the two-step pattern (lemleads_search in companies mode with `description` -> display_leads), you MUST pass the returned `dataRef` to this tool. The server resolves it to the full company set server-side and injects a `currentCompanyByIds` filter so the leads table queries leads at those companies. This persists in the workspace item (no 30-min TTL concern). Do NOT copy the `companyIds` array into a `currentCompanyByIds` filter inline — emitting hundreds of IDs inline risks truncation; pass them via the `companyIds` field instead and the server builds the filter for you. FOLLOW-UP TURNS over an existing companies table ("find CEOs of those companies", "now show me leads from this list", "and the marketing directors"): the workspace already holds the company set, so DO NOT re-run lemleads_search. Recovery order: 1. If the companies-mode lemleads_search `dataRef` from earlier is still in your context AND was issued in the last ~30 minutes, pass it as `dataRef`. 2. Otherwise, call `get_workspace_items({ itemType: "companies" })`, read `data.companyIds` from the workspace_companies item, and pass that array via the `companyIds` field on this tool. The server builds `currentCompanyByIds` for you (no truncation risk). Re-running lemleads_search to "re-verify the count" loses the curated set and is what produces the bad / off-topic lead lists. Otherwise (no prior companies table involved), call display_leads directly with filters only — the table loads data dynamically. Pass any combination of `filters`, `dataRef`, and `companyIds` together; they are AND-combined server-side. To modify an existing LEADS search: get_workspace_items -> display_leads with updated filters + replaceItemId. Only pass replaceItemId when the target item is itself a leads table — never to convert a companies/messages/other item into leads. To show a different artifact type alongside, omit replaceItemId so a new item is appended.

    Inputs

    titlestringrequired
    Title for the table tab (e.g. "CFOs in France", "SaaS Founders")
    columnsarray
    Optional column keys to display as a string[] (e.g. ["name","title","company"]). Pass the key strings, not column-descriptor objects. Defaults to name, title, company, etc.
    companyIdsarray
    Company IDs to scope the leads to. The server builds a currentCompanyByIds filter from these. Accepts both People-Database company IDs (numeric — the recovery path when a prior companies dataRef is expired but the workspace_companies item still holds them: get_workspace_items -> data.companyIds) and lemlist CRM company IDs (cpn_… from search_companies), which the server resolves to People-Database IDs via coreSignalId/domain. When some CRM IDs cannot be resolved, the result reports how many; report that to the user and do NOT claim success on a resultCount of 0. Avoids LLM-side truncation that you would get by inlining a long currentCompanyByIds filter.
    dataRefstring
    Reference to a previous lemleads_search result. For a people-mode search, the server REPLAYS the exact verified filter set, so passing this dataRef guarantees the displayed table matches the totalCount you just verified (no filter drift, no retyping). For a companies-mode (vector) search, the server injects a currentCompanyByIds filter from the resolved company set. Preferred over retyping filters or inlining IDs (avoids LLM truncation and search/display drift).
    filtersarray
    People Database filters (PREFERRED). Use filters from get_lemleads_filters. Each needs filterId, in, out arrays.
    leadIdsarray
    Specific lead IDs to display. Only use for small known lists (<50). For results from lemleads_search, pass dataRef instead — it avoids truncating large arrays in the tool call arguments.
    replaceItemIdstring
    ID of an existing workspace leads item to replace (use for modifications instead of creating a new item)
  • lemlistmcp_display_leads_pageFetch one page of People Database leads for the workspace-leads table.Read-only

    Display Leads Page

    Fetch one page of People Database leads for the workspace-leads table. App-only: called by the leads grid iframe (pagination, select-all), not by the agent. Returns the raw Elastic hits the iframe transforms into rows, the total count, and the already-in-campaign / already-in-contacts maps.

    Inputs

    filtersarrayrequired
    Resolved People Database filters (filterId/in/out) for the search
    pagenumberrequired
    0-based page index (the usage:"ui" API contract)default 0
    sizenumberrequired
    Results per page (default 25, max 100)default 25
    seedstring
    Optional ordering seed for stable pagination
  • lemlistmcp_display_tableDisplay a data table in the workspace panel.Read-only

    Display Table

    Display a data table in the workspace panel. CRITICAL RULES — follow exactly or the table will be empty: 1. dataRef: If the source tool returned a dataRef string, pass it as dataRef (preferred — avoids large data transfer). The rows will be resolved automatically from the stored reference. 2. rows: Only if no dataRef is available. Pass the ORIGINAL data array from the tool result AS-IS. NEVER construct row objects manually or map/transform the array. 3. columns: Use suggestedColumns from the source tool result directly when available. NEVER invent column keys — they must match actual property names in the row objects. PREFERRED: dataRef=result.dataRef, columns=result.suggestedColumns, groupKey=result.groupKey, tableType=result.tableType FALLBACK: rows=result.campaigns, columns=result.suggestedColumns WRONG: rows=result.campaigns.map(c => ({sentCount: c.stats.sentCount})) — this strips data Always forward groupKey and tableType from the source tool when available.

    Inputs

    columnsarrayrequired
    Column definitions — use suggestedColumns from source tool if available
    titlestringrequired
    Table title (shown in workspace tab)
    appendToItemIdstring
    Workspace item ID of an existing table to append rows to instead of creating a new tab. Use get_workspace_items to find the ID, then pass it here when loading more data.
    dataRefstring
    Reference ID from a source tool (e.g. search_campaign_leads). When provided, rows are resolved automatically from the stored data. Preferred over passing rows directly.
    groupKeystring
    Grouping key for auto-appending rows. Tables with the same groupKey merge into one tab. Use the source tool result value (e.g. campaignId for leads). Required when paginating.
    rowsarray
    Fallback: the ORIGINAL data array from the source tool result, passed as-is. Only use when dataRef is not available.
    tableTypestring
    Type of table data. Both values render an "Export as CSV" button for the user: "campaign_leads" for leads coming from search_campaign_leads (server-side export), and "generic" for any other table (the displayed columns and rows are exported client-side). Forward the value from the source tool result when there is one; for a custom table you assembled yourself (e.g. an enriched inbox analysis), set "generic" so the user gets the export button.one of campaign_leadsgeneric
  • lemlistmcp_find_watch_list_linkedin_urlsFind real, validated LinkedIn URLs to monitor for a watch list type.Read-only

    Find Watch List Linkedin Urls

    Find real, validated LinkedIn URLs to monitor for a watch list type. Returns LinkedIn URLs (person /in/ or company /company/ depending on the type), sourced and validated from live web search and grounded in the team's business context (AI Context Center). Use when the user asks about: - Pre-filling the LinkedIn URLs for a linkedinPeopleProfile, linkedinCompanyProfile, competitorConnections or competitorReactions watch list - Discovering relevant LinkedIn profiles or company pages to watch Important: - Only the 4 LinkedIn signal types are accepted; any other type is rejected. - Pass an optional "focus" to steer the search; otherwise it falls back to the type's name. - Returns an empty list when no relevant URLs are found. - Rate-limited per user per day (a limited number of calls).

    Inputs

    typestringrequired
    Watch list type to find LinkedIn URLs for. Only the 4 LinkedIn signal types are supported: linkedinPeopleProfile (returns /in/ person profiles), linkedinCompanyProfile, competitorConnections, competitorReactions (return /company/ company pages).one of linkedinPeopleProfilelinkedinCompanyProfilecompetitorConnectionscompetitorReactions
    focusstring
    Optional free-text focus to steer the search (e.g. "HR SaaS in France"). Defaults to the type's feature name when omitted.
  • lemlistmcp_get_ai_variable_promptsRead the AI-generation prompts of a campaign's AI variable columns (the AI-generated columns in the Leads table: icebreakers, openers, contextual openers, etc.).Read-only

    Get Ai Variable Prompts

    Read the AI-generation prompts of a campaign's AI variable columns (the AI-generated columns in the Leads table: icebreakers, openers, contextual openers, etc.). Returns one entry per AI variable: its variable name, the prompt used to generate it, and the AI model. Prompts come back verbatim, with raw {{variable}} placeholders (e.g. {{firstName}}) left unresolved, since a prompt is a template applied across all leads (there is no single lead to resolve against). Use when the user asks to: - See how an AI column (icebreaker, opener, ...) is generated for a campaign - Review or audit the prompt behind a campaign's AI variables before editing it Read-only: no credits consumed, nothing is changed. To edit a prompt, use update_ai_variable_prompt; to add a new column, use create_ai_variable_prompt.

    Inputs

    campaignIdstringrequired
    Campaign ID (cam_xxx format) whose AI variable prompts to read