Salesbot — LinkedIn MCP Server

Servidor MCP do LinkedIn e MCP do Sales Navigator para descoberta de leads, divulgação aprovada por humanos, fluxos de trabalho de caixa de entrada e um CRM de vendas integrado com 48 ferramentas protegidas por salvaguardas de segurança.

Documentação

Servidor MCP do LinkedIn com CRM (Salesbot)

O que é isto? linkedin-mcp-server-salesbot é um servidor MCP (Model Context Protocol) para operações de relacionamento no LinkedIn assistidas por IA. Ele permite que assistentes de IA — Claude Desktop, ChatGPT, Cursor — ajudem você a pesquisar e organizar contatos profissionais, redigir mensagens profundamente personalizadas para sua revisão e aprovação, sincronizar conversas da caixa de entrada, enriquecer perfis e buscar contexto da web — tudo sob sua orientação. Foi criado para abordagens hiperdirecionadas e significativas (encontre 5 contatos ideais, leia as postagens recentes deles, escreva 5 notas atenciosas), não para disparos em massa. Cada envio é controlado por aprovação humana no fluxo e limites de segurança diários/horários aplicados no servidor que mantêm sua conta do LinkedIn dentro de limites seguros.

Ele roda como uma Supabase Edge Function (Deno + Hono + mcp-lite) expondo o transporte MCP Streamable HTTP. As ações no LinkedIn passam por um provedor de integração terceirizado; as credenciais do LinkedIn nunca são armazenadas pela IA.

  • Palavras-chave: model context protocol, mcp server, linkedin api, linkedin automation, claude desktop, cursor, ai agents, sales automation.
  • Clientes compatíveis: Claude Desktop, Claude API/MCP, Cursor, qualquer cliente MCP Streamable-HTTP.

Fatos rápidos

Endpointhttps://app.salesbot.cz/api/mcp
TransporteMCP Streamable HTTP (POST + SSE)
Cabeçalho de autenticaçãox-mcp-api-key: sb_mcp_… (um JWT do Supabase em Authorization também funciona)
Número de ferramentas49
LicençaMIT

Como me conectar? (Claude Desktop / Cursor)

Adicione isto à configuração do seu cliente MCP. Obtenha a chave sb_mcp_… no aplicativo Salesbot em Configurações → 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"
      }
    }
  }
}

Importante: envie a chave no cabeçalho x-mcp-api-key, não em Authorization: Bearer. O gateway de API do Supabase rejeita tokens Bearer desconhecidos antes que eles cheguem ao servidor.

Autenticação

  • Chave de API MCP (sb_mcp_…) — de longa duração; gerada no aplicativo Salesbot, armazenada apenas como hash SHA-256. Envie em x-mcp-api-key.
  • JWT do Supabase — um token de sessão de usuário conectado em Authorization: Bearer.
  • É necessária uma assinatura/avaliação ativa.

Como autentico o LinkedIn?

A IA pode fazer isso sem sair do chat:

  1. Chame get_linkedin_status — informa se o LinkedIn está conectado/ativo/bloqueado.
  2. Se não estiver conectado, chame connect_linkedin — retorna um link https://auth.salesbot.cz/… com a marca personalizada. O usuário abre, conclui o login no LinkedIn, pronto.

Ou conecte no aplicativo: Configurações → LinkedIn → Conectar.

Ferramentas

Cada ferramenta retorna conteúdo de texto; erros retornam { "ok": false, "code": "<CODE>", "error": "<message>" }.

Conexão

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

Descoberta de leads

{ "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": "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": "get_job_posting_details",   "input": { "job_id": "string (required)" } }
{ "name": "scrape_website",            "input": { "url": "string (required)", "max_chars": "number (default 8000, max 20000)" } }

search_google_xray salva os perfis encontrados em uma lista de contatos "Google X-Ray" (deduplicada) e retorna os contact_ids deles — prontos para enriquecer, adicionar a uma campanha ou enviar para o CRM.

search_job_postings pesquisa vagas de emprego no LinkedIn pela conta conectada (busca clássica, sem necessidade de Recruiter). Retorna ofertas de emprego com informações da empresa — ótimo para encontrar empresas que estão contratando ativamente para uma função específica. Combine com search_linkedin_people para encontrar o gerente de contratação.

search_web é uma busca geral do Google (não restrita ao LinkedIn). Use operadores do Google como site:jobs.cz, intitle:, OR para pesquisar portais de emprego, sites de empresas ou notícias. Os resultados NÃO são salvos em contatos — esta é uma ferramenta de pesquisa/descoberta.

get_job_posting_details recebe um job_id de search_job_postings e retorna a vaga completa — principalmente hiring_team, o recrutador ou gerente de contratação que publicou a vaga, com o ID do LinkedIn e se um InMail gratuito está disponível. Também retorna applicants_counter / views_counter como sinais de urgência. Fluxo típico: search_job_postingsget_job_posting_detailsenrich_contacts → campanha.

Contatos

{ "name": "upsert_linkedin_contact", "input": { "profile_url": "string (required)", "full_name": "string", "company": "string", "position": "string", "headline": "string" } }
{ "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)" } }

upsert_linkedin_contact é o caminho idempotente para uma URL de perfil do LinkedIn exata e já conhecida. Ele cria o contato na lista CRM Imports ou retorna o contact_id existente, para que integrações de CRM possam chamá-lo com segurança antes de add_contacts_to_campaign sem depender da busca do Google.

list_lead_lists retorna o list_id, nome, descrição e contagem de contatos de cada grupo. Passe um list_id retornado para list_contacts.

Fluxo de trabalho típico de contatos: list_lead_listslist_contactsadd_contacts_to_campaign. Os contatos ainda pertencem a uma lista de leads, mas adicionar um contato existente a uma campanha requer apenas o contact_id e o campaign_id de destino.

search_linkedin_people e search_linkedin_navigator retornam resultados brutos de busca do LinkedIn. Eles não persistem contatos; chame upsert_linkedin_contact para cada perfil que você quiser salvar ou adicionar a uma campanha.

enrich_contacts extrai o perfil completo do LinkedIn de cada contato pela conta conectada (cargo, localização, empresa e posição atuais, histórico completo de trabalho, educação, habilidades) e o salva no contato. Ótimo logo após search_google_xray.

Campanhas

{ "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, 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)" } }

Mensagens de IA (escrever → aprovar → enviar)

{ "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)" } }

Ações diretas no LinkedIn

{ "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)" } }

Caixa de entrada (tempo real)

{ "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)" } }

CRM (funil, notas, tarefas, armazenamento de mensagens)

O CRM é um funil persistente separado dos contatos. Um lead entra nele quando é adicionado a uma campanha ou quando qualquer uma dessas ferramentas o toca pela primeira vez. Ele também atua como um armazenamento durável para o texto de abordagem gerado: salve rascunhos de e-mail/LinkedIn e acompanhamentos com save_lead_message, leia-os de volta com list_lead_messages ou get_lead_context e envie-os pelo MCP do canal certo (por exemplo, Smartlead para e-mail) — este servidor nunca os envia por conta própria.

{ "name": "set_deal_stage",     "input": { "contact_id": "uuid (required)", "stage": "string (required)", "note": "string" } }
{ "name": "log_crm_note",       "input": { "contact_id": "uuid (required)", "summary": "string (required)", "pain_points": "string[]", "sentiment": "positive|neutral|negative" } }
{ "name": "save_lead_message",  "input": { "contact_id": "uuid (required)", "body": "string (required)", "channel": "email|linkedin", "kind": "string e.g. initial|followup", "subject": "string", "status": "draft|queued|sent", "message_id": "uuid (update existing)" } }
{ "name": "list_lead_messages", "input": { "contact_id": "uuid (required)", "channel": "email|linkedin", "kind": "string", "limit": "number" } }
{ "name": "create_task",        "input": { "title": "string (required)", "contact_id": "uuid", "due_at": "ISO 8601", "details": "string" } }
{ "name": "list_tasks",         "input": { "status": "open|done|cancelled|all", "contact_id": "uuid", "limit": "number" } }
{ "name": "complete_task",      "input": { "task_id": "uuid (required)", "status": "done|open|cancelled" } }
{ "name": "get_lead_context",   "input": { "contact_id": "uuid (required)", "notes_limit": "number" } }
{ "name": "update_contact",     "input": { "contact_id": "uuid (required)", "email": "string", "phone": "string", "location": "string", "company": "string", "position": "string", "headline": "string" } }
{ "name": "set_lead_fields",    "input": { "contact_id": "uuid (required)", "fields": "object { field_key: value }" } }
{ "name": "export_crm",         "input": { "limit": "number (default 5000, max 20000)" } }

get_lead_context retorna o contexto completo de 360° de um lead — perfil, estágio do funil, campos personalizados, mensagens de abordagem salvas, resumos de conversas, tarefas abertas, interações recentes no LinkedIn e histórico de estágios.

Configuração do CRM (estágios e campos personalizados)

Os estágios do funil e os campos personalizados são configuráveis pelo usuário.

{ "name": "list_crm_stages",  "input": {} }
{ "name": "add_crm_stage",    "input": { "label": "string (required)", "color": "hex string" } }
{ "name": "rename_crm_stage", "input": { "key": "string (required)", "label": "string", "color": "hex string" } }
{ "name": "delete_crm_stage", "input": { "key": "string (required)", "reassign_to": "string" } }
{ "name": "list_crm_fields",  "input": {} }
{ "name": "add_crm_field",    "input": { "label": "string (required)", "type": "text|number|date|url" } }
{ "name": "delete_crm_field", "input": { "key": "string (required)" } }

Exemplo de chamada

Solicitação (MCP tools/call):

{ "jsonrpc": "2.0", "id": 1, "method": "tools/call",
  "params": { "name": "get_daily_limits", "arguments": {} } }

Conteúdo de resultado de sucesso (JSON dentro da parte de texto):

{ "profile_active": true,
  "limits": { "connections": { "used": 0, "limit": 30, "effective_limit": 30 },
              "messages": { "used": 0, "limit": 40, "effective_limit": 40 } } }

Conteúdo de resultado de erro:

{ "ok": false, "code": "ACCOUNT_NOT_CONNECTED", "error": "Profile has no connected LinkedIn account." }

Códigos de erro

CódigoSignificado
AUTH_MISSING / AUTH_INVALID / AUTH_EXPIREDchave ausente / incorreta / expirada
SUBSCRIPTION_REQUIREDavaliação expirada ou sem plano ativo
RATE_LIMITEDmuitas solicitações MCP — reduza o ritmo
ACCOUNT_NOT_CONNECTEDo perfil não tem LinkedIn conectado (chame connect_linkedin)
ACCOUNT_BLOCKEDo LinkedIn restringiu a conta (campanhas pausadas automaticamente)
PROFILE_INACTIVE / PROFILE_NOT_FOUND / ACCESS_DENIEDperfil / propriedade
DAILY_LIMIT_REACHED / HOURLY_LIMIT_REACHEDcota atingida
OUTSIDE_ALLOWED_HOURSfora da janela de envio da conta
BLACKLISTEDempresa/domínio de destino na lista negra
APPROVAL_REQUIREDna fila para aprovação humana antes do envio
SAFETY_BLOCKEDo texto parece injeção de prompt / URL não solicitada
REPLY_LIMIT_REACHEDjá há 2 respostas de IA nesta conversa
SCRAPE_LIMIT_REACHEDcota semanal de extração da web atingida
VALIDATION_ERROR / NOT_FOUND / UPSTREAM_ERRORentrada inválida / não encontrado / falha upstream

Segurança e uso responsável

Proteção algorítmica integrada do LinkedIn e limites diários de segurança. Esta é uma ferramenta de relacionamento, não um disparador em massa — foi projetada para enviar algumas mensagens altamente personalizadas e aprovadas por humanos, e o servidor impede ativamente o abuso em massa:

  • Limites diários por conta com aumento gradual para contas novas; limite de MCP por hora; limite geral de taxa de solicitações por usuário.
  • Fila de aprovação humana no fluxo para ações de saída (configurável).
  • Janelas de horários/dias permitidos e atrasos aleatórios de detecção.
  • Defesa contra injeção de prompt: texto não confiável de CRM/caixa de entrada é tratado como dado; texto de saída é verificado antes do envio.
  • Caixa de entrada: máximo de 2 respostas de IA por conversa (anti-transbordamento); respostas são verificadas contra injeção.
  • Proteção da conta: em caso de bloqueio do LinkedIn (403 do provedor), as campanhas são pausadas automaticamente e o usuário é notificado por e-mail.

Perguntas frequentes

Quais clientes de IA funcionam? Qualquer cliente MCP Streamable-HTTP — Claude Desktop, a API do Claude, Cursor e similares.

Por que x-mcp-api-key e não Authorization? O gateway do Supabase valida tokens bearer Authorization e rejeita os desconhecidos; o cabeçalho personalizado passa sem alterações.

A IA vê minha senha do LinkedIn? Não. A autenticação acontece por um fluxo de provedor hospedado (com marca personalizada em auth.salesbot.cz); o servidor MCP usa apenas um identificador de conta.

A IA pode enviar mensagens sem mim? Somente se você desativar a aprovação. Por padrão, as ações de saída ficam na fila para aprovação humana.

É seguro para minha conta do LinkedIn? Limites diários/horários, aumento gradual, janelas de horário permitidas, atrasos aleatórios e pausa automática em caso de bloqueio detectado são todos aplicados no servidor.

Implantação

Roda no backend Supabase do Salesbot. Com a CLI do Supabase:

supabase functions deploy mcp-server --no-verify-jwt --project-ref <your-project-ref>

Segredos de função necessários: SUPABASE_URL, SUPABASE_SERVICE_ROLE_KEY, SUPABASE_ANON_KEY, as credenciais do provedor do LinkedIn, CRON_SECRET, APP_URL. O servidor faz sua própria autenticação, daí --no-verify-jwt.

Licença

MIT — consulte LICENSE.