LinkedIn MCP Server with CRM (Salesbot)

MCP serverProductivity

Your AI can run LinkedIn outreach that you approve message by message, with a built-in sales CRM to keep prospects organized. Safety gates and anti-spam safeguards keep the outreach responsible.

Use LinkedIn MCP Server with CRM (Salesbot) in Claude, ChatGPT or Ahel Desktop

Free. Sign in, add LinkedIn MCP Server with CRM (Salesbot) and connect your AI. About a minute.

Also: Claude Code · Cursor · Codex

Then ask your AI: use LinkedIn MCP Server with CRM (Salesbot) to search linkedin people

Details

Available today. Use it from your connected AI after setup.

Add it, then ask your AI to draft an outreach message for a prospect on LinkedIn. Review and approve each message before it goes out, and track the contact in the built-in CRM.

LinkedIn MCP Server with CRM (Salesbot)Start free

What your AI can do with it

  • Draft LinkedIn outreach messages for your review
  • Send a message only after you approve it
  • Track contacts and deals in the built-in sales CRM
  • Keep outreach within safety gates and anti-spam guardrails
  • Manage your sales pipeline through your AI

Install LinkedIn MCP Server with CRM (Salesbot)

The server’s own address, for the clients that take one directly. Or connect Ahel once and every client you use reads it from one address, with the account kept on Ahel rather than in each client’s config.

  • Claude Code

    claude mcp add --transport http --scope user linkedin-mcp-server-with-crm-sal 'https://app.salesbot.cz/api/mcp'

    Run it once in your project, then open /mcp to approve any sign-in the server asks for.

  • Claude Desktop

    https://app.salesbot.cz/api/mcp

    Add a custom connector in Settings, paste this address, and approve the sign-in.

  • Cursor

    cursor://anysphere.cursor-deeplink/mcp/install?name=linkedin-mcp-server-with-crm-sal&config=eyJ1cmwiOiJodHRwczovL2FwcC5zYWxlc2JvdC5jei9hcGkvbWNwIn0=

    Open the link and Cursor adds the server at that address.

  • ChatGPT

    https://app.salesbot.cz/api/mcp

    In Settings, enable Developer mode, create an MCP app, and paste this address. Your plan and workspace must allow custom apps.

  • Codex

    codex mcp add linkedin-mcp-server-with-crm-sal --url 'https://app.salesbot.cz/api/mcp'

    Run it once, then sign in with codex mcp login linkedin-mcp-server-with-crm-sal if the server asks for an account.

From the project's README

As published by kubis010/linkedin-mcp-server-salesbot in README.md.

What is this? linkedin-mcp-server-salesbot is a Model Context Protocol - MCP server for AI‑assisted LinkedIn relationship operations. It lets AI assistants — Claude Desktop, ChatGPT, Cursor — help you research and organize professional contacts, draft deeply personalized messages for your review and approval, sync inbox conversations, enrich profiles, and pull web context — all under your direction. It's built for hyper‑targeted, meaningful outreach (find 5 ideal contacts, read their recent posts, write 5 thoughtful notes), not bulk blasting. Every send is gated by human‑in‑the‑loop approval and enforced, server‑side daily/hourly safety thresholds that keep your LinkedIn account within safe limits.

It runs as a hosted service at app.salesbot.cz and speaks the MCP Streamable HTTP transport. Users sign in with OAuth 2.1 (or use an API key in clients that cannot sign in). LinkedIn actions go through a third‑party LinkedIn integration provider; LinkedIn credentials are never stored by the AI.

Product page and documentation: https://salesbot.cz/en/mcp-server

  • Keywords: model context protocol, mcp server, linkedin api, linkedin automation, claude desktop, cursor, ai agents, sales automation.
  • Compatible clients: Claude Desktop, Claude API/MCP, Cursor, any MCP Streamable‑HTTP client.

Quick facts

Endpointhttps://app.salesbot.cz/api/mcp
TransportMCP Streamable HTTP (POST + SSE)
AuthOAuth 2.1 sign-in, or x-mcp-api-key: sb_mcp_… header
Tool count82
LicenseMIT

Connect with sign-in (OAuth): claude.ai, Cowork, Claude Code

The server supports OAuth 2.1, so no key needs to be copied:

  • claude.ai / Cowork: Customize → Connectors → + Add → Add custom connector → name Salesbot, URL https://app.salesbot.cz/api/mcp → Continue → authentication Sign in now (keep the recommended OAuth client; if sign-in fails choose Register automatically) → Add. Sign in to Salesbot and allow access on the consent page, then switch Salesbot on under + → Connectors in a chat. On Team/Enterprise an owner adds it first (Organization settings → Connectors → Add → Custom → Web) and members click Connect.
  • Claude Code plugin (adds three skills on top of the connection: salesbot-outreach, salesbot-campaign and salesbot-email):
claude plugin marketplace add Kubis010/linkedin-mcp-server-salesbot
claude plugin install salesbot@salesbot

Then start claude, run /mcp, select the Salesbot server and sign in.

To update later, refresh the marketplace first (otherwise the old version is reported as current):

claude plugin marketplace update salesbot
claude plugin update salesbot@salesbot

You can see and remove connected apps in the Salesbot app under Settings → MCP → Connected apps. The plugin contains no code that runs on your machine; it only talks to https://app.salesbot.cz/api/mcp.

ChatGPT and Codex

For a private ChatGPT connection, open Plugins on chatgpt.com (chatgpt.com/plugins) → + → Add custom MCP server, enter the name Salesbot, the server URL https://app.salesbot.cz/api/mcp and choose OAuth. Confirm the warning, choose Create as a plugin, complete Salesbot consent in the browser, install the plugin and call it in a chat with @Salesbot. Availability depends on your ChatGPT plan and workspace settings. The discovery endpoints are available, but end-to-end ChatGPT sign-in has not yet been verified by this repository's readiness check.

The portable OpenAI package is defined by plugin.json and mcp.json; it reuses the existing workflows and logo without changing the Claude package. It is a submission candidate, not an approved public listing. See the OpenAI readiness and submission checklist for tests, review-account requirements and the third-party integration policy gate.

Clients that cannot sign in can use an API key, see below.

What the plugin runs and sends

  • Runs nothing on your machine. The plugin is three Markdown skills and one remote MCP server entry; there are no hooks, scripts or binaries.
  • Talks to one server: https://app.salesbot.cz/api/mcp (Salesbot, operated by Sales Robots s.r.o.). Sign-in uses OAuth 2.1 on app.salesbot.cz.
  • What reaches Salesbot: the arguments of the tools Claude calls (for example a search query, a contact id, the text of a message or e-mail you asked to send). Salesbot then acts in your connected LinkedIn account and e-mail tools within your limits and approval settings. It does not read your conversation or local files.
  • Data handling: see the privacy policy and terms. Remove access at any time in the Salesbot app → Settings → MCP → Connected apps.

Connect with an API key (Cursor, Codex, Claude Desktop config)

Add this to your MCP client config. Get the sb_mcp_… key in the Salesbot app under Settings → MCP.

{
  "mcpServers": {
    "linkedin-automation": {
      "url": "https://app.salesbot.cz/api/mcp",
      "headers": {
        "x-mcp-api-key": "sb_mcp_YOUR_API_KEY",
        "Accept": "application/json, text/event-stream"
      }
    }
  }
}

Important: send the API key in the x-mcp-api-key header, not Authorization: Bearer; Authorization is reserved for OAuth access tokens.

Authentication

  • OAuth 2.1 (authorization code + PKCE, dynamic client registration). A request without credentials gets 401 with WWW-Authenticate: Bearer resource_metadata="https://app.salesbot.cz/.well-known/oauth-protected-resource"; clients discover the authorization server from there. The access token goes in Authorization: Bearer.
  • MCP API key (sb_mcp_…) — long‑lived; generated in the Salesbot app, stored only as a SHA‑256 hash. Send in x-mcp-api-key.
  • An active subscription/trial is required.

How do I authenticate LinkedIn?

The AI can do it without leaving the chat:

  1. Call get_linkedin_status — reports whether LinkedIn is connected/active/blocked.
  2. If not connected, call connect_linkedin — returns a white‑labeled https://auth.salesbot.cz/… link. The user opens it, completes LinkedIn login, done.

Or connect in the app: Settings → LinkedIn → Connect.

Tools

Each tool returns text content; errors return { "ok": false, "code": "<CODE>", "error": "<message>" }.

Why 82 tools. Salesbot covers LinkedIn outreach, e-mail and a CRM, and each tool does exactly one thing. The tools are grouped into the areas below, and a client only needs the area it is working in (Claude can load tools on demand). Every tool description starts with when to use it and names the tool to use instead where two are easy to confuse (for example search_linkedin_people vs search_linkedin_navigator, or upsert_linkedin_contact vs add_crm_contact). Read-only tools are kept separate from write and delete tools, so clients can show correct read-only and destructive hints and ask for approval only where it matters. The salesbot_campaign_operator prompt contains the same map.

AreaTools
Connection and limits4
Lead discovery (LinkedIn, Google, jobs, web)7
Contacts and lead lists11
Campaigns and messaging16
Direct LinkedIn actions and inbox7
E-mail5
CRM people, tasks and notes12
CRM companies11
CRM setup, export, blacklist9

Connection

{ "name": "get_linkedin_status", "input": { "profile_id": "uuid (optional)" } }
{ "name": "connect_linkedin",   "input": { "profile_id": "uuid (optional)", "reconnect": "boolean (optional)" } }

Lead discovery

{ "name": "search_linkedin_people",    "input": { "title": "string (required)", "location": "string", "locationId": "string", "network": "['S'|'O']", "limit": "number 1-50" } }
{ "name": "search_google_xray",        "input": { "jobTitle": "string (required)", "location": "string", "keywords": "string[]", "excludeWords": "string[]", "limit": "number 1-100" } }
{ "name": "search_linkedin_navigator", "input": { "search_url": "string (required)", "limit": "number 1-100" } }
{ "name": "import_linkedin_company_list", "input": { "search_url": "string (required)", "list_name": "string (required)", "limit": "number", "cursor": "string", "save_to_crm": "boolean" } }
{ "name": "list_company_lists",          "input": {} }
{ "name": "list_companies",              "input": { "company_list_id": "uuid (required)", "limit": "number", "offset": "number", "query": "string" } }
{ "name": "update_company_research",     "input": { "prospect_company_id": "uuid (required)", "web_research": "string (required)", "website": "string", "domain": "string", "research_urls": "string[]" } }
{ "name": "search_job_postings",       "input": { "keywords": "string (required)", "location": "string", "locationId": "string", "seniority": "string[]", "job_type": "string[]", "presence": "string[]", "date_posted": "number", "easy_apply": "boolean", "limit": "number 1-50" } }
{ "name": "search_web",                "input": { "query": "string (required)", "limit": "number 1-30", "country": "string (default cz)", "language": "string (default cs)" } }
{ "name": "read_company_website",      "input": { "url": "string (required)", "extra_urls": "string[] (max 3, same domain)" } }
{ "name": "get_job_posting_details",   "input": { "job_id": "string (required)" } }

search_google_xray saves the profiles it finds into a "Google X-Ray" contact list (deduplicated) and returns their contact_ids — ready to enrich, add to a campaign, or push into the CRM.

search_job_postings searches LinkedIn job postings via the connected account (Classic search, no Recruiter needed). Returns job offers with company info — great for finding companies actively hiring for a specific role. Combine with search_linkedin_people to find the hiring manager.

search_web is a general-purpose Google search (not restricted to LinkedIn). Use Google operators like site:jobs.cz, intitle:, OR to search job portals, company websites, or news. Results are NOT saved to contacts — this is a research/discovery tool.

read_company_website reads a company's own public website – the homepage plus up to 3 contact/about/services pages on the same host – and returns a short extract with source URLs and generic company e-mails (info@, sales@ …); personal e-mails and phone numbers are masked and no employee lists are collected. It identifies as SalesbotBot, honours robots.txt (and does not read a site whose robots.txt cannot be checked), noindex/noai and TDM reservations, reads only public addresses, stops at logins, CAPTCHAs and paywalls, and is limited to 60 reads per account per day. Nothing is stored; save the summary with update_company_research. A found address is not consent to contact. Rules: salesbot.cz/en/terms#website-reading.

get_job_posting_details takes a job_id from search_job_postings and returns the full posting — most importantly hiring_team, the recruiter or hiring manager who posted the role, with their LinkedIn id and whether a free InMail is available. Also returns applicants_counter / views_counter as urgency signals. Typical flow: search_job_postings → get_job_posting_details → enrich_contacts → campaign.

Company/account-list workflow: import_linkedin_company_list → list_companies → search_web → update_company_research → search_linkedin_people → upsert_linkedin_contact with the returned prospect_company_id. Companies are stored separately from people and can enter CRM before a decision-maker is known.

Contacts

{ "name": "upsert_linkedin_contact", "input": { "profile_url": "string (required)", "full_name": "string", "company": "string", "position": "string", "headline": "string", "prospect_company_id": "uuid" } }
{ "name": "get_contact_profile", "input": { "contact_id": "uuid (required)" } }
{ "name": "list_lead_lists",    "input": {} }
{ "name": "list_contacts",       "input": { "list_id": "uuid (required)", "limit": "number", "offset": "number" } }
{ "name": "enrich_contacts",     "input": { "contact_ids": "uuid[] (required, max 8)", "profile_id": "uuid (optional)" } }
{ "name": "create_lead_list",    "input": { "name": "string (required)", "description": "string" } }
{ "name": "add_contacts_to_list", "input": { "list_id": "uuid (required)", "contact_ids": "uuid[] (required)" } }
{ "name": "remove_contacts_from_list", "input": { "contact_ids": "uuid[] (required)", "list_id": "uuid" } }
{ "name": "sync_linkedin_connections", "input": { "cursor": "string", "sync_id": "string", "limit": "number ≤500" } }
{ "name": "list_blacklist",      "input": { "query": "string", "limit": "number", "offset": "number" } }
{ "name": "check_blacklist",     "input": { "contact_id": "uuid", "company": "string", "domain": "string" } }
{ "name": "delete_contacts",     "input": { "contact_ids": "uuid[] (required)", "confirm": "true (required)", "delete_crm_leads": "boolean", "force": "boolean" } }

Every contact lives in exactly one list: add_contacts_to_list moves contacts, remove_contacts_from_list moves them back to CRM Imports (nothing is deleted). sync_linkedin_connections pages through your own 1st-degree connections (max 500 per page, paced by the server). list_blacklist / check_blacklist read the company-wide blacklist.

delete_contacts deletes permanently, including campaign history. Call it only on the user's explicit request, after showing them the contacts, with confirm: true. To stop outreach to someone, use exclude_contacts_from_campaign instead. upsert_linkedin_contact is the idempotent path for an exact, already-known LinkedIn profile URL. It creates the contact in the CRM Imports list or returns the existing contact_id, so CRM integrations can safely call it before add_contacts_to_campaign without relying on Google search.

list_lead_lists returns each contact group's list_id, name, description and contact count. Pass a returned list_id to list_contacts.

Typical contact workflow: list_lead_lists → list_contacts → add_contacts_to_campaign. Contacts still belong to a lead list, but adding an existing contact to a campaign only requires its contact_id and the target campaign_id.

search_linkedin_people and search_linkedin_navigator return raw LinkedIn search results. They do not persist contacts; call upsert_linkedin_contact for each profile you want to save or add to a campaign.

enrich_contacts reads each contact's LinkedIn profile via the connected account and saves the headline, location and current company & position. Following GDPR data minimisation, Salesbot does not store photos, the "About" text, education, skills, earlier jobs or contact details. Great right after search_google_xray.

Campaigns

{ "name": "list_campaigns",           "input": { "status": "draft|running|paused|completed|stopped (optional)" } }
{ "name": "create_campaign",          "input": { "name": "string (required)", "profile_id": "uuid (required)", "description": "string", "daily_limit": "number", "sender_context": "string", "steps": "[{ action: 'connect'|'message'|'visit', delay_hours, use_ai, ai_prompt, ai_template, message_mode: 'template'|'creative', send_without_message }] (required)" } }
{ "name": "update_campaign_settings", "input": { "campaign_id": "uuid (required)", "name": "string", "description": "string", "daily_limit": "number", "sender_context": "string", "auto_approve_messages": "boolean", "status": "running|paused|draft|stopped" } }
{ "name": "start_campaign",           "input": { "campaign_id": "uuid (required)" } }
{ "name": "stop_campaign",            "input": { "campaign_id": "uuid (required)" } }
{ "name": "add_contacts_to_campaign", "input": { "campaign_id": "uuid (required)", "contact_ids": "uuid[] (required)" } }
{ "name": "exclude_contacts_from_campaign", "input": { "campaign_id": "uuid (required)", "contact_ids": "uuid[] (required)", "reason": "string" } }
{ "name": "list_campaign_queue",      "input": { "campaign_id": "uuid (required)", "limit": "number" } }

message_mode decides how a message step uses its sample (ai_template): template sends the sample word for word and the AI only fills its fields (e.g. [Firma] → "Škoda Auto"); creative has the AI write a new message for each lead from ai_prompt, with the sample only as a style example. The campaign's sender_context ("About me") is used only where the prompt says {{o_mne}}. Links in the prompt or sample are kept as written.

exclude_contacts_from_campaign stops all further outreach to those contacts but keeps their history. list_campaign_queue shows each contact's state, invitation status and the next tool to call.

AI messaging (write → approve → send)

{ "name": "prepare_campaign_messages", "input": { "campaign_id": "uuid (required)", "limit": "number ≤10" } }
{ "name": "generate_campaign_message", "input": { "campaign_contact_id": "uuid (required)", "step_id": "uuid (required)", "custom_instructions": "string" } }
{ "name": "list_pending_approvals",    "input": { "campaign_id": "uuid", "limit": "number" } }
{ "name": "approve_message",           "input": { "campaign_contact_id": "uuid (required)", "edited_messages": "[{step_id, message}]", "skip_gpt_check": "boolean" } }
{ "name": "reject_message",            "input": { "campaign_contact_id": "uuid (required)", "reason": "string (required)" } }
{ "name": "list_mcp_pending_actions",  "input": { "action_type": "string", "limit": "number" } }

prepare_campaign_messages queues drafts for up to 10 pending contacts per call; repeat while remaining_pending > 0. When the campaign has AI approval on (auto_approve_messages), Salesbot checks each draft — the contact's name and gender, company, leftover placeholders, signature and grammar — and approves only what passes; the rest waits in list_pending_approvals. list_mcp_pending_actions lists one-off messages and invitations waiting for the user's approval (read-only).

E-mail (your own mailbox: Outlook or IMAP/SMTP)

{ "name": "list_email_integrations", "input": {} }
{ "name": "send_email",              "input": { "provider_campaign_id": "mailbox_id (required)", "body": "string (required)", "subject": "string", "provider": "mailbox", "crm_lead_id": "uuid", "contact_id": "uuid", "email": "string" } }
{ "name": "list_email_outreach",     "input": { "status": "string", "crm_lead_id": "uuid", "contact_id": "uuid", "limit": "number" } }
{ "name": "get_email_status",        "input": { "outreach_id": "uuid (required)" } }
{ "name": "cancel_email",            "input": { "outreach_id": "uuid (required)", "reason": "string" } }

send_email sends a personal e-mail to one person from your own mailbox: pass the mailbox_id from list_email_integrations. Salesbot sends it within your sending hours, a few minutes apart and capped per day. Smartlead and Instantly are no longer supported. Consent: in the Czech Republic a commercial e-mail needs the recipient's prior consent unless they are an existing customer (§ 7 of Act 480/2004 Coll.) — use LinkedIn for first contact with new people.

Every e-mail is checked before it is sent: the greeting (right name, and pane/paní by the contact's gender), the company, leftover placeholders such as {{company}}, Czech grammar, and no signature in the body when the mailbox adds its own. A failed check returns AI_CHECK_FAILED with the reason and nothing is sent; fix it and call again. A second failure goes to the user's manual approval. If approval is required, the e-mail waits as pending_approval. cancel_email withdraws an e-mail that has not gone out yet.

Direct LinkedIn actions

{ "name": "send_connection_request", "input": { "linkedin_id": "string (required)", "profile_id": "uuid (required)", "contact_id": "uuid" } }
{ "name": "send_linkedin_message",   "input": { "linkedin_id": "string (required)", "message": "string ≤5000 (required)", "profile_id": "uuid (required)" } }
{ "name": "publish_linkedin_post",   "input": { "profile_id": "uuid (required)", "text": "string ≤3000 (required)", "external_link": "string", "as_organization": "string", "auto_publish": "boolean" } }
{ "name": "get_daily_limits",        "input": { "profile_id": "uuid (optional)" } }

publish_linkedin_post has its own server-side safety limit of 1 published post per LinkedIn account in a rolling 60-minute window. This applies to both personal and organization posts and is reported as the posts quota by get_daily_limits. When the quota is exhausted, the tool returns HOURLY_LIMIT_REACHED and does not publish the post.

Inbox (real‑time)

{ "name": "list_inbox_chats",  "input": { "profile_id": "uuid (optional)", "limit": "number 1-50", "cursor": "string" } }
{ "name": "get_chat_messages", "input": { "chat_id": "string (required)", "profile_id": "uuid (optional)", "limit": "number 1-50", "cursor": "string" } }
{ "name": "reply_to_chat",     "input": { "chat_id": "string (required)", "message": "string ≤5000 (required)", "profile_id": "uuid (optional)" } }
{ "name": "mark_chat_read",    "input": { "chat_id": "string (required)", "profile_id": "uuid (optional)" } }

Shortened here. Read the whole README on GitHub.

Tools it offers (80)

What this server listed when Ahel dialed its public endpoint in Oct 2026, with no key and no account of yours. The names are the server’s own.

  • search_linkedin_people
  • sync_linkedin_connections
  • search_google_xray
  • upsert_linkedin_contact
  • add_contacts_to_list
  • remove_contacts_from_list
  • enrich_contacts
  • search_linkedin_navigator
  • import_linkedin_company_list
  • list_company_lists
  • list_companies
  • update_company_research
  • add_companies_to_crm
  • list_crm_companies
  • get_crm_company
  • update_crm_company
  • merge_crm_companies
  • delete_crm_leads
  • delete_contacts
  • delete_crm_company
  • add_contacts_to_crm
  • list_email_integrations
  • list_email_campaigns
  • send_email
  • list_email_outreach
  • get_email_status
  • cancel_email
  • get_contact_profile
  • list_contacts
  • search_crm_leads

Signals

GitHub stars
1
Last commit
Sep 2026
Advanced
Delivery
linkedin-mcp-server MCP server → your Ahel connector (mcp.ahel.ai) → your AI.
Item type
mcp-server
Key
cz-salesbot-linkedin-mcp-server
Source
github.com/kubis010/linkedin-mcp-server-salesbot
Hosted endpoint
https://app.salesbot.cz/api/mcp